6 ms·
This might be influenced by the fact that I've used C/C++ for much longer, but my impression is that Rust is an even larger language with more details to learn
by pavon 3y ago
This might be influenced by the fact that I've used C/C++ for much longer, but my impression is that Rust is an even larger language with more details to learn than C++. The difference is that in Rust if you get the details wrong (in safe code) you get a compiler error or at worst a logic bug, while in C++ sometimes you get a compiler error and sometimes you get memory corruption or integer overflows, or undefined behavior.
Expanding on this, the general concepts you need to understand for both are about the same, but because Rust enforces them in the compiler, you have to learn the detailed rules of how the language enforces ownership and lifetimes and such which is more detailed/complicated/restrictive than the concepts themselves.
Furthermore, some of the Rust language details cause library APIs to become more involved than they would be in C++. An example in the std library is that in C++ handling errors and function results are orthogonal features. With sum types they are intertwined, and the Rust community is more liberal with adding convenience functions and syntax so you end up with a combinatorial explosion of all the different ways you want to handle the error combined with all the ways you want to handle the result, with about 40 methods each in Result and Option, plus methods for handling errors in iterators (functional streams). Lifetimes and async can also complicate crate APIs in ways that don't exist in C++. None of them are difficult on their own, but the shear volume of things you need to learn an remember makes me appreciate the minimal Go philosophy.
On the flip side, the places where C++ gets more complex are all the little bad decisions that can't be fixed for backwards compatibility like the stupid numeric conversion rules inherited from C which can easily bite you. And both the C and C++ string/stream libraries suck in their own ways.
- trealira 3y ago> This might be influenced by the fact that I've used C/C++ for much longer, but my impression is that Rust is an even larger language with more details to learn than C++. The difference is that in Rust if you get the details wrong (in safe code) you get a compiler error or at worst a logic bug, while in C++ sometimes you get a compiler error and sometimes you get memory corruption or integer overflows, or undefined behavior. I disagree. Rust seems less complicated in many ways. For example, move semantics are a lot simpler; they're always a byte copy, whereas with C++, you have to remember rvalue references, lvalue references, and universal/forwarding references (which are usually rvalue references but sometimes are lvalue references). You also have to be careful not to mess with a moved-from object, as it's in an unspecified state. C++ also makes a distinction between trivially copyable types and non-trivially copyable types (a distinction Rust doesn't make). It's difficult to remember the rules for non-trivially copyable classes, but they boil down to "if it has a user-provided constructor, destructor, or copy-assignment operator; or if it has virtual functions; then it's not trivially copyable." In Rust, all you have to remember is that types are by default moveable (and moves are a memcpy), deep copies are implemented through the Clone trait, and if your type can't be memcpy'd (e.g., a self-referential struct) it needs to only be accessible through Pin pointers, which ensure that it isn't memcpy'd in safe code. Another thing: you can cause a use-after-free error by combining coroutines with lambdas [1]. An error like this only happens because C++'s rules around coroutines and lambdas are complicated enough that the committee didn't forsee this happening. This seems indicative of higher complexity in C++ than Rust, to me. [1]: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95111#c23 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95111#c23
- pavon 3y agoOn the other-hand, I find it annoying in Rust that an assignment or an unadorned function parameter might result in a move or a copy and you can't tell by the function signature or call site. Instead it depends on whether the type implements the Copy trait, which is a big semantic difference based on "spooky action at a distance". > You also have to be careful not to mess with a moved-from object, as it's in an unspecified state. This is a great example of Rust being easier, even when it just as complex. Both languages don't allow you to use an object after it is moved, so it is the same amount to learn and to think about when writing code, but Rust will give you a helpful compile-time error while C++ will let you blow your foot off.
- kaba0 3y ago> but Rust will give you a helpful compile-time error while C++ will let you blow your foot off. But Rust can actually analyze trivially when this happens, while it is not possible with C++ due to language semantics.
- fluoridation 3y ago>You also have to be careful not to mess with a moved-from object, as it's in an unspecified state. This is incorrect, somewhat. It's "unspecified" in the sense that the standard doesn't mandate that user-defined move constructors and assignment operators leave source objects in any particular state. All standard library classes are left in well-defined states when moved (if they can be moved), and you can choose to define your classes to do the same. The usual rule of thumb is that a moved object should be in the same state as if it had just been default-constructed. This is what all the standard classes do. Move semantics in C++ and in Rust are more or less equivalent. The major differences are that C++ copies by default and rust moves by default, and that Rust doesn't allow using a moved object while C++ does. >In Rust, all you have to remember is that types are by default moveable (and moves are a memcpy), deep copies are implemented through the Clone trait, and if your type can't be memcpy'd (e.g., a self-referential struct) it needs to only be accessible through Pin pointers, which ensure that it isn't memcpy'd in safe code. If defining value semantics was so simple you wouldn't have had to bring up Pin pointers (which I assume are not just pointers, but something special that needs to be kept in mind), or the fact that Rust understands that there are two different kinds of code, which C++ doesn't distinguish. It's suddenly so obvious that one is simpler than the other. >Another thing: you can cause a use-after-free error by combining coroutines with lambdas [1]. An error like this only happens because C++'s rules around coroutines and lambdas are complicated enough that the committee didn't forsee this happening. This seems indicative of higher complexity in C++ than Rust, to me. It's trivially easy to cause use-after-free errors with lambdas. Return an std::function<void()> that captures and reads a local std::unique_ptr by reference and then call operator()() on the object. This is not a complex interplay between features; lambdas necessarily introduce dynamic lifetimes into a language that was originally not designed to support them, so using them requires care.
- kaba0 3y agoWhat is the equivalent of that Forest Gump GIF of the guy listing the 20+ c++ initializers in Rust?