7 ms·
As someone who coded C++ (15+ years) and later Rust (about 4 years now) for my dayjob: there are more than those you listed I have seen commonly. Unaligned acce
by VorpalWay 6d ago
As someone who coded C++ (15+ years) and later Rust (about 4 years now) for my dayjob: there are more than those you listed I have seen commonly. Unaligned accesses is a perrenial favourite, as is ODR violations and reliance on the undefined order of static constructors between translation units. In C++ I didn't see much of unsafe casts, except related to enums (always specify an underlying type to mitigate this).
Rust protects against all of these, but if you think Rust is only about memory safety, I don't believe you have seriously tried it. It does a lot of things in std API design as well to steer you away from bugs. Some examples:
- The pervasive use of Result and Option makes it impossible to forget to handle (or forward) the error case.
- Because of usage of RAII (C++ has this too, but not as pervasively, C doesn't except using some very new GCC extension) it is very hard to forget to free resources such as files, sockets, database connections, mutexes, etc.
- Enums can carry payload in their variants (C devs: think tagged unions, but safe, C++ devs: think std::variant but with match/case rather than bulky visitor pattern), which means you can make API designs that cannot represent invalid states.
- The typestate pattern is a bit hard to explain briefly, but it allows a state machine with types at compile time, to make sure you dont misuse an API. For example it can be used to prevent forgetting setting required fields in a builder before building. Or in embedded microcontrollers to make sure you can't hand out the same GPIO pin to different parts of the code base.
I often find that my code in Rust works first try, while that almost never happen in C++ for non-trivial code. It is what all those Haskell devs were talking about all these years, but in a systems language (no GC is critical to my day job in hard realtime control systems) and without the incomprehensible abstract math lingo.
- uecker 6d agoI was only speaking for C not C++ (I fled C++ a long time ago). My code usually works first try in C, but my experience also working with students is that you need to learn to use safe patterns and strategies first. I can imagine that Rust enforces those. You can do a lot more in C too: You can design safe interfaces based around incomplete structure types. This also should allows what you call typestate pattern (if I understand it correctly). You can build a decent option type / result type. You can have a bounds safe vector type. You can have safe string types. One can have type-safe dynamic casts. One can annotate return values so that they can't be ignored. One can use many different tools for safety. People coming from C++ often think that one can not do this in C because "it lacks abstractions", but this is not really true.
- VorpalWay 6d agoIt has been a long time since I coded C. Last I did I remember hating C strings and the standard functions for dealing with them. So easy to get buffer overflows. I'd rather have the language design be so that I dont constantly have to think about not tripping over various things, instead I want to focus on the hard and interesting domain specific problems. EDIT: Also, errno is an awful design. Forgetting to check for errors, or screw up which error you report is so easy in C. Exceptions in C++ are also bad, it is very easy to have no idea what exceptions are possible 5 layers down and end up with unhandled exceptions.
- uecker 6d agoTrue, if you do pointer arithmetic on C strings or similar low-level buffer operations without introducing safe abstractions, there is basically no way to do this safely.
- VorpalWay 6d agoAnd I think that illustrates my point well. Yes there is MISRA C++ and CERT for C when you write safety critical code. But that is a lot of extra rules to follow and remember (and have linting tools check where possible). It is basically a entirely separate dialect of the parent languages. And if you aren't doing safety critical you won't be dealing with these but have to come up with your own (company specific or individual) rules. (You dont want to write MISRA C++ unless you have to, large parts of it are quite miserable). In Rust I get good defaults, and a language that guides me in the right direction. The rules for safety critical rules are somewhat still under development but so far they look a lot shorter (you still need the "don't allocate in hard realtime tasks except at startup" and similar rules for example). And if you aren't doing safety critical you can safely use all of the language as long as you stay away from unsafe. And for most code you dont need unsafe, and even when you do someone else has likely done the hard work for you already, providing safe abstractions on top. (The exception is FFI to other languages, it is impossible to avoid unsafe when calling code in another language that the compiler can't reason about, you should build a safe Rust API on top of the raw bindings. For popular libraries this often already exists.)