Levi's blog

References and & in Rust

Levi Notik

In Rust, there are two types of references: a shared reference and a mutable reference. A reference is denoted by &. A mutable reference is denoted as &mut.

The docs tell us that a 'reference lets you refer to a value without taking ownership of it.' What does that mean? What is ownership?

Let's look at a really dumb, simple example.

fn main() {
    let x = "cool";
}

We declared a variable x which refers to the string literal "cool". Plain and simple variable assignment. "cool" is a value and the Rust docs tell us that every value in Rust has a variable called its owner. In our example, the value "cool" is owned by x. Said another way, x is the owner.

Actually, that is the first of three ownership rules which are:

  1. Each value in Rust has a variable that’s called its owner.
  2. There can only be one owner at a time.
  3. When the owner goes out of scope, the value will be dropped.

So x is the owner ("cool" is owned by x). What this means is that "cool" lives as long as x does. So when x is dropped "cool" is dropped.

Is there was a way to see this concretely, some way to see this in action? Let's try.

fn main() {
    let y = "hi";
    let x = "cool";
    println!("{}", y);
    println!("{}", x);
}

We added another variable y and then printed x and y. So far so good, no problems. If we add braces around the declaration of x

    let y = "hi";
    {
        let x = "cool";
    }
    println!("{}", y);
    println!("{}", x);

our code no longer compiles and we get an error pointing to println!("{}", x);. The error says "cannot find value x in this scope." A scope or "scope block" is the region in which a variable binding is valid. Our program fails because we created a new scope delineated by the curly braces around x and x lives in this scope and not outside of it. x is invalid outside of the braces we introduced so we can't use it in that println! The body of main is itself a block and everything inside of its braces lives in its scope.

What's this have to do with ownership and dropping? Well, let's skip to rule #3. It says that when an owner goes out of scope, its value will be dropped.' In our example x goes out of scope at that closing brace so it is dropped and, since it owns the value "cool", that gets dropped too.

There's actually a trait called Drop which has a function drop that gets called when something goes out of scope. Let's define a simple struct just so we can implement Drop for it so we can see when it gets called.

#[derive(Debug)]
struct Foo(u32);

impl Drop for Foo {
    fn drop(&mut self) {
        println!("Dropping {:?}", self);
    }
}

fn main() {
    let f = Foo(1);
    {
        let g = Foo(2);
    }

}

When we run this, it prints

Dropping Foo(2)
Dropping Foo(1)

which shows that g is dropped before f which makes sense given the rules stated above.

Now let's make things more interesting by defining a function that takes a Foo.

fn gimme_a_foo(f: Foo) {
    println!("{:?} is a nice lookin' Foo.", f);
}

and then let's use it:

fn main() {
    let f = Foo(1);
    gimme_a_foo(f);
}
$ cargo run
...
Foo(1) is a nice lookin' Foo.

gimme_a_foo is such a fun function, let's call it twice!

fn main() {
    let f = Foo(1);
    gimme_a_foo(f);
    gimme_a_foo(f);
}

and run it

$ cargo run
...
error[E0382]: use of moved value: `f`
  --> src/main.rs:19:17
   |
17 |     let f = Foo(1);
   |         - move occurs because `f` has type `Foo`, which does not implement the `Copy` trait
18 |     gimme_a_foo(f);
   |                 - value moved here
19 |     gimme_a_foo(f);
   |                 ^ value used here after move

error: aborting due to previous error

Wait... what?

The problem is that rule #2 tells us that resources can only have one owner. But, ownership can be transferred by assignment or by passing an argument by value.

When we start out, the value Foo(1) is owned by f. Then, when we call gimme_a_foo(f);, the ownership of f was "moved" to gimme_a_foo. And when a resource has been moved, the previous owner cannot be used. This is a very good thing because by enforcing this, Rust ensures that we never have dangling pointers (pointing to a memory location that's already been deleted/freed).

One quick fix is to return ownership of the resource after using it. We can modify our function to do this:

fn gimme_a_foo(f: Foo) -> Foo {
    println!("{:?} is a nice lookin' Foo.", f);
    f
}

fn main() {
    let f = Foo(1);
    let ff = gimme_a_foo(f);
    gimme_a_foo(ff);
}

That works, but it's obviously a giant pain-in-the-ass. This is where references come in! We said at the beginning of this post that references let you refer to a value without taking ownership which sounds like exactly what we want! We use & to denote a reference.

fn gimme_a_foo(f: &Foo) {
    println!("{:?} is a nice lookin' Foo.", f);
}

fn main() {
    let f = Foo(1);
    gimme_a_foo(&f);
    gimme_a_foo(&f);
}

By using &f instead of f we've created a reference to the value of f without taking ownership of it. For this to work, the signature of our function had to change to reflect that the type of the f parameter is a reference.

If we didn't change gimme_a_foo in this way, it wouldn't work. The problem would have nothing to do with calling gimme_a_foo twice, it'd be an issue even calling it once because the types just don't match up:

fn gimme_a_foo(f: Foo) { // expects a Foo
    println!("{:?} is a nice lookin' Foo.", f);
}

fn main() {
    let f = Foo(1);
    gimme_a_foo(&f); // but we passed an &Foo
}

So we'd get the following compiler error:

note: expected type `Foo`
              found type `&Foo`

So by changing our function to take a reference to a Foo, we never take ownership in the first place. Another way to think about this is in terms of blocks or scopes which discussed earlier. Our original function was:

fn gimme_a_foo(f: Foo) {
    println!("{:?} is a nice lookin' Foo.", f);
}

the f argument has a scope which is the body of the function (the block is the area enclosed by { and }) so what happens is gimme_a_foo takes ownership of f and then it goes out of scope which is why we can't do the double call to gimme_a_foo. But when we changed the function parameter to &Foo, we're saying that we are NOT taking ownership.

When a function parameter takes a reference to something, Rust calls this "borrowing".

Back to top
Close Bitnami banner
Bitnami