I mean this from a high-level perspective, how this feature presents itself to users. Its semantics depend on a particular specific definition of what is let
's value, and what counts as mutation in this case. This is quite specific, and misaligned with what users may think "value" and "mutation" are. From user perspective, let name = &mut String::new()
and let name = String::new()
may both be "the name", and name.push_str("…")
"mutate" "the name". But without getting into language-lawyer-level details, it makes let mut
seem arbitrary. In both cases name.push_str()
calls the same &mut self
method on a String
, and in the problem domain, the "name" has been "mutated" either way.
You keep assuming I have issues with `let mut`, because I just don't understand what is happening…
BTW, we've had this conversation before, so I need to repeat: I know exactly what's happening here and why. I know about temporary lifetime extension. I know the difference between a reference and a value, and that the exclusive reference is the value in one of the cases. I know let mut
makes a distinction between creating a loan to its value, and the use of its value for reborrowing that doesn't take a reference to the reference-being-the-value. I know the .
operator's auto deref hides the levels of indirection, and the reasons why it's like this. I know these things, in fine detail. I've been using Rust since 0.x. I've slogged through the entire Dragon Book, and have written compilers before. I know about the bindings, binding modes, values, references, places, shadowing, identifiers, lexical scoping, namespacing, copies, moves, and temporaries.
You keep assuming I have issues with let mut
, because I just don't understand what is happening, and keep trying to explain Rust to me. But I know precisely what is happening, I just think that this particular combination of behaviors, minutiae of Rust's definitions, and interactions with other features combined, sucks. I'm not mistakenly assuming it behaves differently. I know it's behaving exactly as designed. I just think this design is poor, and it should behave differently.
I know it doesn't, I'm fully aware how it works, and appreciate why Rust has shadowing. I just think this combination of features makes let
's guarantees borderline useless.
To me one of the important use-cases of making variables immutable is being able to assume that the thing with this identifier in this scope stays constant, but Rust doesn't have that, without many caveats .
In Rust seeing the same name later in the source code (within the scope of the binding) doesn't guarantee that this is this the same instance of the binding, so whatever let
declared may be irrelevant. This weakens what let
can help with in practice.
And even when I can establish that an identifier is for the same instance of an "immutable" binding, it's immutable only by a very shallow definition of mutation specific to let
, which isn't even as strong as immutability of &
references.
So to know whether something has been mutated (in loose terms) between its "immutable" let
and its name appearing later in the scope, I can't rely on the let
stating anything. I still need to scan all the source in between to find potential shadowing, and additionally either know the type (which thanks to type inference and auto deref may vary, and not be locally available) or carefully analyze code for mutations myself too.