Learn Rust: Assignment and Memory Semantics

If you are familiar with Java or C/C++, understanding the assignment operator (=) in Rust will demystify a lot of its memory semantics. Assignment in Rust can have a range of outcomes depending on what you are assigning from and to, and on whether you are using the reference operator (&) and the mut keyword (short for mutable). In this post I will describe some scenarios of the assignment operation in Rust.

But first, two definitions:

  1. When you instantiate an object and assign it to a variable, only that variable controls write access privileges to the memory region associated with that object. This is called Ownership.
    Eg: let p = Person {age: 30}; // only p can grant write access to the Person object.

    Only one owner can exist for a memory region at any point, and the memory region’s destructor is called when that (sole) owner goes permanently out of scope.

  2. You can create multiple references (a.k.a aliases) to an existing object, not too unlike C/C++ pointers or references. References won’t invoke the destructor even after they go out of scope.
    Eg: let p_ref = &p;  // p_ref is a (read only) reference to the Person object above

What is very different in Rust compared to C/C++ and Java/Go?

  1. If you have a class (like Person above) and do not explicitly implement the Copy trait, assigning a new variable to an existing owner variable makes the older variable unusable from then on!
    Eg:
      let p = Person {age: 30};
      let mut q = p; // Create a new, read-write owner for p's memory. Any use of p after this line is a compiler error!

  2. Whether a line of code is valid can depend on what subsequent lines of the program do!
    Eg:
      /* Line 1 */ let mut p = Person {age: 30}; // Create a read-write owner variable
      /* Line 2 */ let p_ro_ref = &p; // Create a read-only ref to p
      /* Line 3 */ println!("{}", p_ro_ref.age);
      /* Line 4 */ p.age += 1; // NOTE: This line won't compile because of the next line!
      /* Line 5 */ println!("{}", p_ro_ref.age);


    In the above, Line 4 flags a compiler error. You cannot mutate Person on Line 4, because the compiler detects that an already created Read-Only ref will be used even after the mutation. If you comment out Line 5, the compiler figures out that the Read-Only ref is not used beyond Line 3, and will let you mutate on Line 4. Observe how different this is from the scoping rules you may be used to in other languages!

Given that we have established four types of entities (Read-Only/Read-Write owners/refs), the table and state diagram below lay out how the assignment operation across all of them work. Each row also contains a link to relevant code in the Rust playground where you can try it out.

If you are new to Rust, take some time to evaluate whether each behaviour below makes logical sense and how they fit in together.

Assign FromAssign ToEffect on Source varTry It
1Read Only OwnerRead Only OwnerPermanently invalidatedLink
2Read Only OwnerReadWrite OwnerPermanently invalidatedLink
3ReadWrite OwnerRead Only OwnerPermanently invalidatedLink
4ReadWrite OwnerReadWrite OwnerPermanently invalidatedLink
5Read Only OwnerRead Only RefBecomes RO, Non-Movable till dest can’t be expiredLink
6Read Write OwnerRead Write RefBecomes unusable till dest can’t be expiredLink
7Read Write OwnerRead Only RefBecomes RO, Non-Movable till dest can’t be expiredLink
Table of memory semantics of assignments in Rust
State diagram for the different kinds of assignments in Rust.
Each arrow represents an assignment going from the right side to the left side of an assignment statement

You can find the code for the above examples in this Github repository: rust-ownership-model

Leave a Reply

Your email address will not be published. Required fields are marked *