Mutability and ownership

Hello guys,
please help me understand a simple thing.
Why an unmutable variable should let move ownership?
Example:

let a = String::from("a string");
let b = a;

Is the variable a, now, in an uninitialized state as I read around on a lot of books? If so, the value should not be movable out from a!

Can anyone help me with understand that?

Unlike most other programming languages, rust will prevent you from using a after the value was moved. This guarantees no double frees.

Then you couldn't pass a to another function or return it (at least, without more special rules which would indirectly let you move it to a new binding anyway).

The mut binding limitations can be viewed as a fatal lint against overwriting or taking a &mut to a binding (variable). Otherwise the variable is the same, e.g. there is no type difference. If the variable is itself a &mut _ or if interior mutability is involved, you can also mutate through a non-mut binding.

(&T and &mut T, on the other hand, are distinct and significantly different types.)

In Rust, an immutable variable can be

  • initialized once
  • then used immutably for as long as you want[1]
  • deinitialized (i.e. moved out of) once

Often times, initialization coincides with declaration, and deinitialization coincides with dropping at the end of the scope.

{
    let a = String::from("Hello World"); // declaration and initialization
    println!("{a}"); // use immutably
} // end of scope, value of `a` is dropped here

But you can initialized late or deinitialize early.

{
    let a; // declaration
    // let _ = &a; // trying to read `a` at this point would be a compilation error
    println!("variable a about to be initialized");
    a = String::from("Hello World"); // initialization
    println!("{a}"); // use immutably
    drop(a); // de-initialize early; could also move a to other function or variable
    println!("a has been dropped already");
    // let _ = &a; // trying to read `a` at this point would be a compilation error
} // end of scope, value of `a` was already moved-out-of so nothing happens here

Whether or not you interpret these two state changes (uninitialized -> initialized) / (initialized -> uninitialized) as a form of “mutation” of course could constitute a philosophical discussion of programming language design–anyways, this is how Rust’s design sees it: it’s not a mutation.

In my personal view, this makes sense. The main point of marking a variable[2] as immutable is that at any place where the variable is used, you can be certain it still has the same value that it was initialized with.[3]

Rust prevents access to a variable before it was initialized or after it has been deinitialized, hence this property that “you can be certain it still has the same value that it was initialized with” is always true, even if late initialization or early deinitialization is permitted.

The main strength of this “you can be certain it still has the same value that it was initialized with” property is that when reading code, you can easily just jump to the “definition” of an immutable variable to get its meaning, without needing to look at all the other places it's mentioned in between. To that end, early deinitialization poses the least concern in my view, and other features like shadowing, or late initialization, make it harder to find this “definition”. (In the case of shadowing, IDE support to “jump to definition” can still help avoid any issues; and late initialization is used not too commonly, anyways, because let is allowed in the middle of blocks already.[4])


  1. ignoring types that might contain interior mutability ↩︎

  2. again, ignoring the case where interior mutability is involved, though most types do not contain any interior mutability, anyways, so you’ll know it if you’re doing it, generally speaking ↩︎

  3. In most other programming languages, there is a second main point of immutability markers: they limit the scope of possible shared mutably accessed values. Rust already prevents shared mutability outright, anyways, unless you use it explicitly via interior mutability primitives, so Rust uses the type system to this end; and variable mutability thus becomes exclusively a concern of code readability. Many Rustaceans thus also consider variable mutability as more of a very strictly enforced lint.

    This difference of Rust vs. other languages also motivates why Rust’s design has seen no need for marking fields in structs as immutable yet. (Such a language feature wouldn't be entirely useless, and proposals for it do exist, but it's still much less important than in languages that don't limit shared mutability as heavily as Rust does.) ↩︎

  4. so its only real use case is when you need a variable to live “higher up” in the stack in an outer block, or if you need some conditional initialization, e.g. for some trickery to create &dyn Trait references for a handful of possible types, without heap allocations ↩︎

5 Likes

I like some real-world analogies here.

I usually think of my books as immutable -- I wouldn't mark them mut. That's because I like keeping them in good condition. No dog-ears, no broken spines, etc.

If I give ownership (aka "move") of the book to someone else though, it's no longer my business. I no longer have access to the book at all (the compiler keeps me from looking at it since I moved it) so I can't even tell whether someone else modified it.

Since you're prevented from caring after it's been moved, everything is fine. It's not a property of the book that it's mutable, it's how the owner treats it.

1 Like

Ignore let mut. This is only a lint, and it could as well not exist at all, without any changes of semantics or consequences for the language. It would not affect Rust’s correctness or the type system at all.

It’s just a bunch of ad-hoc shallow checks to make programmer declare their intentions about a variable, but whether the data behind it is actually mutable or not is decided differently — by the type system. When you own a value exclusively you can always mutate it, and a lack of mut can’t stop you.

let mut x = x;

Is perfectly legal.

{not_mut}.mutate() is also allowed despite use of an “immutable” binding, because temporaries can always be mutated without extra syntax.

The lite “are you sure?” let mut is quite different from the &mut exclusive references, which have very specific strict, important semantics that are enforced, and you can’t similarly fool them by replacing or shadowing a & reference with &mut.

1 Like

Hi all.

I think that Steffahn got my point.

My question was: "if a variable is not mutable then how can a variable loose ownership of its own value?" I thought "A variable must be mutable to be able to let move ownership to another variable also because it will be uninitialized after the move (it changed its state!)". Basically and trying to sum it up in a few words what Steffahn says is that the compiler will cover and manage the "uninitialize state" preventing you to access the variable after the move (the discussion might also be philosophical, probably I agree on that), dropping the mut keyword on a variable declaration is just for the programmer to say the value a variable holds will not change.

Thank you.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.