I do not contest on the opinion that Rust is a good language, but it slightly hurts me when people club C and C++ together. One can easily write correct by construction code using modern C++. Use of meta-programs allows you to create typesafe constructs. It provides you with zero cost abstractions to specify ownership of resources and ..... <I can go on> One has to just strive to not use the C baggage that comes with it.
> One has to just strive to not use the C baggage that comes with it.
This is why they get clubed together, regardless how much I like C++, I am yet to see the use of C baggage being successfully forbidden in enterprise teams, let alone if there are third party dependencies (which is always the case).
So far I have only seen modern, safe C++ being used successfully on a big project I was part of at CERN, where everyone on the team actually cared to write proper C++.
C++ is much better, but still not safe. Null still exists. Moves are runtime moves, so you can still attempt to access the value at compile time. Iterator invalidation is still an issue.
Of course, C++ might be "safe enough" for your use cases.
> Moves are runtime moves, so you can still attempt to access the value at compile time.
What do you mean by that ? Are you saying the standard decided to make it non-destructive moves ? Then yes, it is a possible scenario for error but also a performance advantage in some cases.
> What do you mean by that ? Are you saying the standard decided to make it non-destructive moves ?
You can still access a value after it has been moved out. use-after-move is allowed by the compiler. It places (stdlib) types in an unspecified but valid state (I've seen C++ code reusing these types and assuming that the state after move is something in particular -- it's not).
Most optimizations you can do by reusing a moved value could be done automatically by the compiler (by using the same stack space since it statically knows that it's been moved out).
Linear types are a pretty well known pattern and could have been used as a part of the design of moves. This would have the additional benefit of removing explicit move constructors from 99% of all types out there. This has not been done (and can't be now).
Edit: The pretty rare optimization potential of runtime moves is pretty much negated by the fact that common things like reallocing a vector can't be made into a memcpy for most types, just because they have nontrivial move ctors which wouldn't exist in the compile-time move scenario.
Use-after-move is allowed because of existence of value categories, if you use a return value from a function its creation is usually elided or it is moved, but you can't access that temporary without directly referencing it. When you move an existing object (by specifically saying std::move), what should happen with it? Destroying the object is not a solution because its variable might be still accessible in existing scope (something like dangling a reference in the middle of a scope), which imply use-after-move should be prohibited be language and all variables should have a "not-a-value" state and throw exceptions on use.
> When you move an existing object (by specifically saying std::move), what should happen with it?
You don't destroy it, you treat it as an actual "move", where the compiler will not consider the original variable as accessible after this point, and not allow accesses after the move. The memory it took up is free for reuse, and no destruction code is run on the side of the code doing the moving (in the case of a conditional move this gets a tiny bit more complex, but not too much). "It's variable will be still accessible in local scope" is exactly what I'm getting at; you can enforce at compile time that this isn't the case by simply disallowing access.
You shouldn't have local references active, just like how you shouldn't have local references to the contents of a vector when you push to it. You can already invalidate local references to a part of a struct when something gets moved in modern C++. Being wary of invalidating local references is an established concept in C++; this doesn't exacerbate that problem.
It's even better if you track scopes in references which gets rid of the dangling reference problem entirely but at this point you've reinvented Rust :p
To be clear, I'm talking of a completely different model that could have been used in place of the rvalue reference and move model. Making C++ have linear types now would be tough, but it could have been done before. There are different tradeoffs there, but I suspect it would have been safer.
> all variables should have a "not-a-value" state and throw exceptions on use.
You can make this a compile time error. Like I said, linear typing is a pretty well established pattern.