In Rust, you can only hold either one mutable reference or a bunch of immutable references to x.
For example:
int x = 42;
x += 1; // ok
const int& r1 = x; // ok
x += 1; // Error
const int& r2 = x; // ok
int& r3 = x; // Error
Conversation
or
int x = 42;
int& r1 = x; // ok
r1 += 1; // ok
const int& r2 = x; // error
int& r3 = x; // error
This restriction roots out the possibility of race conditions. And as far as I know, no other major languages (not just C++) have enforcement in this way.
1
3
Replying to
This seems very strange to me… why would one guard things this carefully when they are not actually being shared?
3
2
Mutable references can invalidate other references by freeing allocations, changing the variant in a tagged union, etc. The issue isn't only data races. So, for example, a mutable reference to a std::vector can push to it resulting in use-after-free via other references, etc.
2
3
I feel this is a bit because of the “almost C pointer” like style? This isn’t how one uses a std::vector in my experience
3
As an example, if you're looping over a collection in C++, if you accidentally modify the collection, you probably end up with a use-after-free.
C++ code does far more allocations/copies than Rust rather than using lightweight references largely because it's hard to do it right.
1
2
If you heavily use std::string_view in C++ as you would use &str in Rust, it would be great for performance but it's begging for having use-after-frees everywhere.
C++ copy constructors being the default helps to encourage allocating/copying. It's just the default approach.
1
2
Rust has to enforce mutable references not overlapping with other usable references because there are a bunch of ways that leads to memory corruption. Data races are only one of the ways that can happen. Use-after-free is incredibly common in C and C++, including complex ones.
1
2
Some mutable reference pulls the rug out from other references by assigning a new string or other dynamically allocated type which invalidates other references into it. It could involve a data race between threads, but really doesn't need to.
1
2
Or modifying them in a way that invalidates the other references (push_back on std::vector, etc.), etc. Rust heavily uses tagged unions and assigning another variant type invalidates references to the existing one which would now be pointing at data for a different type.


