Ownership

Ownership is probably the highest hurdle for most developers investigating Rust. Rust is the first language to gain mainstream acceptance that fundamentally changes how memory is used relative to the last five or six decades. Ownership is tectonic shift in how developers think as they write code.

To understand why ownership is so different, let’s consider the historic approach to programs using memory. Since the early days of programming, memory has been a bit like cooks in a kitchen. One cook in the kitchen can do whatever they like and doesn’t have to worry about another cook “corrupting” things. Two cooks in the kitchen can become surprisingly complicated, but they can make it work if they are each very careful – much more careful than when they have the kitchen to themselves. A little carelessness can be immediately disastrous (i.e., grease fire), or can take a bit of time (i.e., water dripping into a plugged sink).

Multiple cooks’ shared access to the kitchen is analogous to multiple accessors to the same bit of memory in a program. Consider this C# code:

   string s1 = "Hello!";
   string s2 = s1;

   Console.WriteLine($"\tString value on heap, s1 = {s1}");
   Console.WriteLine($"\tString value on heap, s2 = {s2}");
   Console.WriteLine($"\ts1 & s2 reference the same object? {object.ReferenceEquals(s1, s2)}");

Output

    String value on heap, s1 = Hello!
    String value on heap, s2 = Hello!
    s1 & s2 reference the same object? True

The two variables s1 & s2 both reference the same memory location which contains “Hello!” Printing the value of either variable produces the same “Hello!”

The problem is that either variable can change the data at the memory location, and the other only cannot do anything to protect the value or itself. (We’re ignoring copy on write behavior in this case)

Continuing the analogy, Rust only allows a single cook in the kitchen. The developer can instruct one cook to take ownership of the kitchen from another. The previous cook no longer has any access to the kitchen at all. Directly translating the C# code to Rust yields:

    let s1 = String::from("Hello!");
    let s2 = s1;

    println!("\tString value (heap) s1 = {}", s1);
    println!("\tString value (heap) s2 = {}", s2);

Attempting to compile this code will fail with

error[E0382]: borrow of moved value: `s1`

This code looks very similar to the C# example. What’s different? Ownership!

On line 1, s1 is set to “Hello!” After line 1, s1 owns the memory location holding “Hello!” But then on line 2, s2 takes ownership from s1. That’s the situation the Rust compiler has detected, and gives an error telling us that s1 no longer has access to the data. Admittedly, the message is a little cryptic, but it will make more sense after we cover Borrowing.

By commenting out line 4, compilation completes successfully because s1 is no longer attempting to access memory owned by s2. Running the code results in:

String value (heap) s2 = Hello!

One thought on “Ownership

  1. […] Rust’s Ownership model for data is a major departure from previous programming languages and approaches. This model is so fundamental to Rust, that it influences just about everything – syntax, libraries, etc. To some degree, everything about Rust is downstream of Ownership. For a quick primer on Ownership, see this post. […]

Leave a Reply