/ RUST

Yet another Rust ownership tutorial

One of the most important concepts to master in Rust is ownership and borrowing. Tons and tons of articles are solely dedicated to this narrow subject. This one tries to explain the concept with examples. I hope it helps you.

Ownership is a set of rules that govern how a Rust program manages memory. All programs have to manage the way they use a computer’s memory while running. Some languages have garbage collection that regularly looks for no-longer-used memory as the program runs; in other languages, the programmer must explicitly allocate and free the memory. Rust uses a third approach: memory is managed through a system of ownership with a set of rules that the compiler checks. If any of the rules are violated, the program won’t compile. None of the features of ownership will slow down your program while it’s running.

— https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html[What Is Ownership?^]

First taste of ownership

Let’s start with a simple Java snippet:

public void own(String text) {}

public static void main(String[] args) {
    var text = "my text";
    own(text);
    System.out.println(text);
}

Even with a passing knowledge of Java, the code is straightforward.

We can "translate" the above snippet to Rust:

fn own(_: String) {}

fn main() {
    let text: String = String::from("my text");         (1)
    own(text);
    println!("{}", text);
}
1 The String::from() function takes a type reference to create a String

Rust code looks pretty similar. Yet, the compiler complains:

error[E0382]: borrow of moved value: `text`
  --> src/main.rs:11:20
   |
 9 |     let text: String = String::from("my text");
   |         ---- move occurs because `text` has type `String`, which does not implement the `Copy` trait
10 |     own(text);
   |         ---- value moved here
11 |     println!("{}", text);
   |                    ^^^^ value borrowed here after move
   |
note: consider changing this parameter type in function `own` to borrow instead if owning the value isn't necessary
  --> src/main.rs:6:14
   |
 6 | fn own(text: String) {}
   |    ---       ^^^^^^ this parameter takes ownership of the value
   |    |
   |    in this function
   = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider cloning the value if the performance cost is acceptable
   |
10 |     own(text.clone());
   |             ++++++++

The Rust compiler is very helpful compared to the compilers of other stacks I have used in the past. It explains the problem and pinpoints its exact location: we pass the text variable and the own() function takes ownership of the value. It’s illegal to use it in the println!() macro afterwards.

Clone and Copy

We can follow the compiler’s advice and call clone(), because String implements the Clone trait. clone() creates a deep copy of the structure. The code compiles and runs successfully with one caveat: there are now two String structures. It takes twice as much space in memory.

Note that if the structure implements Copy as well, you don’t need to call clone() explicitly. Let’s create our own type to showcase this:

#[derive(Debug, Copy, Clone)]
struct Dummy {}

fn own(_: Dummy) {}

fn main() {
    let dummy = Dummy {};
    own(dummy);
    println!("{:?}", dummy);
}

Note that making a structure implement Copy will implicitly clone() it every time it needs to move. You can’t undo it on a per-call basis. It can be memory-hungry if you do it for large structures that you pass around frequently. You have been warned.

Passing by reference

So far, we passed structures by value. When we do so, the called function takes ownership of the structure. We can let the function borrow the structure instead, and pass it by reference. As in other languages, one can reference a structure with the & operator. Let’s rewrite the code with reference passing.

#[derive(Debug)]
struct Dummy {}

fn borrow(_: &Dummy) {}

fn main() {
    let dummy = Dummy { };
    borrow(&dummy);
    println!("{:?}", dummy);
}

The code compiles and runs. Borrowing allows reading the structure state.

#[derive(Debug)]
struct Dummy {
    foo: String,
}

fn borrow(dummy: &Dummy) {
    println!("{:?}", dummy.foo);
}

fn main() {
    let dummy = Dummy { foo: String::from("Foo") };
    borrow(&dummy);
    println!("{:?}", dummy);                            (1)
}
1 Prints Dummy { foo: "Foo" }

Mutating data

In the previous section, I described how to read data. What happens if we want to write data?

#[derive(Debug)]
struct Dummy {
    foo: String,
}

fn main() {
    let dummy = Dummy { foo: String::from("Foo") };
    dummy.foo = String::from("Bar");
}

We get the following error:

error[E0594]: cannot assign to `dummy.foo`, as `dummy` is not declared as mutable
 --> src/main.rs:8:5
  |
8 |     dummy.foo = String::from("Bar");
  |     ^^^^^^^^^ cannot assign
  |
help: consider changing this to be mutable
  |
7 |     let mut dummy = Dummy { foo: String::from("Foo") };
  |         +++

In Rust parlance, writing data is known as mutating. To mark the variable as being mutable, we use the mut keyword.

#[derive(Debug)]
struct Dummy {
    foo: String,
}

fn main() {
    let mut dummy = Dummy { foo: String::from("Foo") };
    dummy.foo = String::from("Bar");
}

You may want a function to change the state of a parameter. For that, you need to mark the parameter as mut:

#[derive(Debug)]
struct Dummy {
    foo: String,
}

fn mutate(mut dummy: Dummy) {                           (1)
    dummy.foo = String::from("Bar");
    println!("{:?}", dummy.foo);
}

fn main() {
    let dummy = Dummy { foo: String::from("Foo") };
    mutate(dummy);
}
1 mut fixes the issue

Finally, we can both borrow and mutate. For that, we need to mark both the variable and the parameter as mut.

#[derive(Debug)]
struct Dummy {
    foo: String,
}

fn mutate(dummy: &mut Dummy) {                          (1)
    dummy.foo = String::from("Bar");
    println!("Inside: {:?}", dummy.foo);
}

fn main() {
    let mut dummy = Dummy { foo: String::from("Foo") }; (2)
    mutate(&mut dummy);                                 (3)
    println!("Outside: {:?}", dummy.foo);
}
1 Mark the parameter as mut
2 Mark the variable as mut
3 Pass a mutable reference

Summary

Behavior How?

Take ownership

Pass by value

Duplicate the value

Implement Clone and call clone() explicitly

Automatically duplicate the value

Implement Clone and Copy

Borrow

Pass by reference

Mutate the value

Use mut

Mutate behind a reference

Use mut, mut, and mut

To go further:

Nicolas Fränkel

Nicolas Fränkel

Nicolas Fränkel is a technologist focusing on cloud-native technologies, DevOps, CI/CD pipelines, and system observability. His focus revolves around creating technical content, delivering talks, and engaging with developer communities to promote the adoption of modern software practices. With a strong background in software, he has worked extensively with the JVM, applying his expertise across various industries. In addition to his technical work, he is the author of several books and regularly shares insights through his blog and open-source contributions.

Read More
Yet another Rust ownership tutorial
Share this