error[E0596]: cannot borrow data in a `&` reference as mutable
16 | change_str(&mut s);
| ^^^^^^ cannot borrow as mutable
I am quite confused by this error. The linked error indicates the error is when you try to create a mutable reference to a non-mutable variable (I think in C++ this is equivalent to having a non-const reference to a const object) but I cannot see that this is occurring in this case.
Also, why does the same error not occur with the change_string function?
I have read the Rust book section on ownership and borrowing but obviously I must have misunderstood something.
String literals ( "Hello, Rust") are immutable. The type of s is actually &str, which means the reference you're passing to change_str is &mut &str. Because of some function call magic, the compiler tries to turn this into &mut str but can't, because the inner part is immutable.
Turning it into a String is the right thing to do. You can pass that to change_str even, since String dereferences to str.
There are ways to make &mut str without creating a String, but they're not very useful.
That's probably because you know that string literals in C++ are not const. But they are non-const simply because of compatibility with C, otherwise it would make no sense to turn them non-const-but-don't-try-to-change-them type.
In Rust string literals are immutable and even if you put reference to string literal into mutable string you can not turn them into mutable type.
Because string in Rust is mutable, just like in C++.
If your reference is shared it may never become “mutable, exclusive” reference. And “mutable, exclusive” reference to str are not much useful (even if it may, technically, exist, it can not be created from string literal).
However if one does such things in C or C++ one stands a high chance of creating random crashes or erroneous results that are often hard to track down. Which you will then have to spend time thinking about and debugging.
C and C++ are subject to the same borrowing and lifetime rules as Rust, except they don't show up as compiler errors but as run time failures.
Don't worry, it'll pass. It's rough sailing in the beginning but eventually you reach the state where you look on Rust's compiler error message and think about it “hey, compiler have just stopped me from writing code which I would have debugged for days in C++”.
Even extra-advanced C++ programmers sometimes mix up and forget iterator invalidation rules in C++… and let's not forget about all these rules you have to deal with std::move and std::string_view. Ownership and borrow system in Rust is designed precisely and exactly to prevent just this kinds of issues.
At some point you realize just how often the code you wrote in C++ was actually wrong (and just worked because of happy accident and/or because compiler wasn't trying to hard to destroy optimize your program). They you become a Rust fanboy.
If they result in the immediate run-time failures then you are lucky. More frustrating is the case of “are you kidding me? that's 30 years old code!”… with typical follow-up of “yeah, sure, it was broken for 30 years but have have only just found recently how to destroy optimize it”.
In Rust these things are, basically, contained in the unsafe code and even there you have tools too minimize such surprises.
Actually, this example is not very different between Rust and C++. In C++, a string literal still has type const char, which you can't change, and if you want to append to or otherwise modify a string in place, you have to construct a std::string from it. The exact same thing applies to Rust at the high level, it's just that the specific types are spelled differently.