Win some, lose some though, as the overall development workflow is lightyears ahead of C++, mostly due to tooling
Win some, lose some though, as the overall development workflow is lightyears ahead of C++, mostly due to tooling
Rust decided to have more restrictive generic programming, with the benefit of early diagnostic of mistakes in generic code. C++ defers that detection to instantiation, which allows the generics to be more expressive, but it's a tradeoff. But this is an entirely different design decision to lifetime tracking.
This is true even for expressions that are only evaluated in a compile-time context, since dependently-typed languages do "everything" at compile time anyway, they don't have a phase distinction where you can talk about "runtime" being separate.
A truly dependently typed language performs these checks before instantiation time, by evaluating those expressions abstractly. Code that is polymorphic over values is checked for all possible instantiations, and thus its types can actually depend on values that will not be known until runtime.
The classic example is a dynamic array whose type includes its size- you can write something like `concat(vector<int, N>, vector<int, M>) -> vector<int, N + M>` and call this on e.g. arrays you have read from a file or over the network. The compiler doesn't care what N and M are, exactly- it only cares that `concat` always produces a result with the length `N + M`.
Rust already does have some level of const generic expressions, but they are indeed only possible to instantiate with values known at compile time, like C++.
The difficulty of type checking them symbolically still applies regardless of how they're instantiated, but OTOH it doesn't look like Rust is really trying to go that direction.
Notably Rust type-based generics do this, a key difference wrt. C++ templates. (You can use macros if you want checks after instantiation, of course.)
To be clear this distinction is not unique to dependent types, either. Most languages with some form of generics or polymorphism check the definition of the generic function/type/etc against the constraints, so the compiler can report errors before it ever sees any instantiations. This just also happens to be a prerequisite to consider something "dependently typed."
Typical implementations of dependent types do not generate a separate copy of a function for every instantiation, the way C++ does, so they simply do not need the concrete values in the same way.
My experience has been the other way around. Eclipse-based IDEs from NXP, TI, ST all have out-of-the-box usable tooling integration:
- MCU pinout and configuration codegen
- no need to manually fiddle with linker scripts
- static stack and code size analyzers (very helpful for fitting stuff in low-cost MCUs)
- stable JTAG-based debugging with:
- peripheral registers view (with bitfield definitions)
- RTOS threads view (run status, blocked on which resources, ...)
And yes, these are important enough for me to put up with Eclipse and pre-modern C/C++. I really want to write Rust for embedded but struggling with the tooling all the time didn't help.