4 ms·
I've tried Rust before. in the end I concluded that I don't really need it over C++ for the following reasons: - I tend to store references to other objects us
by cyber_kinetist 4y ago
I've tried Rust before. in the end I concluded that I don't really need it over C++ for the following reasons:
- I tend to store references to other objects using indices instead of pointers/references anyway, so the borrow checker isn't really that important for me. Memory errors that comes from indexing can be caught with runtime assertions, or if you can't use assertions because of performance constraints you can still use sanitizers to debug these issues. (You're probably going to run sanitizers even in Rust anyway if you are using unchecked array indexing...) Further use-after-free errors that come up with reusing indices in an object pool can be caught at runtime using the generational index trick. (See https://kyren.github.io/2018/09/14/rustconf-talk.html https://kyren.github.io/2018/09/14/rustconf-talk.html for the gist of what I'm talking about. The presenter listed this as a reason why Rust is good because the compiler nudges you circumvent the borrow checker to design better code, but for me this was actually a reason why Rust doesn't actually help that much at all: I don't need the guidance of the borrow checker to have this decision in the first place! She's actually giving a reason for why the borrow checker is useless!)
- Also, Rust's compile-time facilities are still not as powerful as C++'s. For example in Rust you can't still do operations with integers in generics in compile time, you need to wait that until the full version of const generics. (This is important if you want to make a convenient math/linear algebra library) SFINAE is dirty but still more powerful than what you could do in Rust with trait bounds, template specialization is also absent in Rust. C++ also has 'constexpr if' which makes type-level programming much simpler than what you need to wrangle in Rust. At least Rust has hygienic macros, but to be honest it's a bit cumbersome to use and seems to be designed as an afterthought. (Overall C++'s templates are 'dirtier' than Rust's traits but are far more powerful. But I really like how languages like Zig and Jai approach compile-time programming though... these are the languages I'm more excited about than Rust or C++)
- Unsafe Rust is still much limited than what you could do in C/C++, but still really hard to write. Even when I have a rough picture about how I would exactly implement a system in low level terms, Rust's complicated semantics around safe/unsafe severely distracts me from actually implementing some actual shit. (Arguably this is the same for modern C++ where you need to know all these weird move/copy semantics to implement even simple generic containers like vector<T>, but the best solution for that is to use simple POD structs as much as possible and suddenly your worries tend to disappear.)
- This is probably a bit too unfair, but I would actually like to use some battle-tested C/C++ libraries right away rather than wait for someone to do the gnarly FFI binding work or even "reimplement in Rust(TM)".
Still, Rust has some nice features like ADTs, pattern matching, and syntax sugar for Error types, so maybe the tradeoffs can be worth it if some of these pain points are solved. Around the time when the full version of const generics is finished I might start exploring Rust again.
- est31 4y ago> C++ also has 'constexpr if' FTR since 1.46.0, Rust also has if inside const contexts (as well as loop and match, but no for loops as they are trait based and traits in const are still being developed): https://github.com/rust-lang/rust/blob/master/RELEASES.md#version-1460-2020-08-27 https://github.com/rust-lang/rust/blob/master/RELEASES.md#ve...
- cyber_kinetist 4y agoThat isn't the same as 'constexpr if'. In C++ you can use 'constexpr if' even in non-constexpr/consteval contexts in order to control code generation, like for example: template<class T> auto foo() { if constexpr(compile_time_compute_value_changing_depending_on_T<T>()) { return Type1(); else { return Type2(); } } You can even change the return type of a function depending on the template parameter! Such is the power of duck-typed templates (as opposed to a strict trait system that Rust has)
- est31 4y agoOh I see that's a way different feature then, that Rust indeed doesn't have. You could in theory build a proc macro that supports a meta-language, but it would not be able interact with any of Rust's type system. Rust doesn't even allow _, the equivalent of auto, for return types of functions at all, even though it has a lot of type deduction (which is I think a good decision because it makes type deduction local, so you know which types you are dealing with by looking at the function header).
- exDM69 4y ago> I tend to store references to other objects using indices instead of pointers/references anyway, so the borrow checker isn't really that important for me. C++ references only help you with null pointers and they are a pain in the ass to try to store as a member in a struct with all the weirdness regarding operator=. C++ references do not help you with use-after-free, accidental concurrent modification or container/iterator invalidation. E.g. consider the following C++ application: std::vector<int> v = { 1, 2, 3 }; int& ref = &v[2]; v.push_back(4); ref = 123; // this will crash if vector is resized by push_back You're not going to reproduce this bug in Rust (without unsafe), the program will not compile. As individuals we can deal with these invisible contracts that are everywhere in C++ (and C) programming. But this kind of discipline does not scale to bigger teams. I kinda agree with you about the "power" of C++ templates vs. Rust generics when it comes to compile time metaprogramming. But for generic programming Rust's traits are very practical and powerful, and with C++ still lacking concepts it's really no match. You mention one of Rust's generics' biggest pain points: it does not work at all when trying to implement functions that work with i32 and i64, or f32 vs f64, let alone f32x4 vs. f64x4 SIMD vectors. For comparison: Haskell has "Integral" and "Floating" type classes (traits) for this, but Rust for some reason does not. FFI is painful with every language, including C++ (for C libraries that don't work well w/ C++ idioms). My experience with Rust FFI and bindgen has been excellent. It auto-generates Rust modules from C headers and even converts doxygen comments to Rustdoc. It was about as easy to build a Rust project with some in-house C libraries than it would've been to write a C or C++ program using them with all the CFLAGS+=-Ipath/to/headers and Makefiles I would've had to deal with. Finally, from a seasoned professional C++ programmer... Rust's tooling blows C++ out of the water. A reasonable build system, linters, API docs, LSP, autoformatting, modules (no more header files). And especially C++'s dysfunctional dependency management is just plain awful (single header libraries or apt-get or whatever aren't a substitute). With a few decades of C++ experience under my belt, I've found Rust to be a huge leap forward on almost all aspects (but not without its pain points).