5
votes

I'm trying to implement a cons list in Rust as an exercise. I've managed to solve all of my compiler errors except this one:

Compiling list v0.0.1 (file:///home/nate/git/rust/list)
/home/nate/git/rust/list/src/main.rs:18:24: 18:60 error: borrowed value does not live long enough
/home/nate/git/rust/list/src/main.rs:18         List::End => list = &*(box List::Node(x, box List::End)),
                                                                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/nate/git/rust/list/src/main.rs:16:34: 21:2 note: reference must be valid for the anonymous lifetime #1 defined on the block at 16:33...
/home/nate/git/rust/list/src/main.rs:16 fn add(mut list: &List, x: uint) {
/home/nate/git/rust/list/src/main.rs:17     match *list {
/home/nate/git/rust/list/src/main.rs:18         List::End => list = &*(box List::Node(x, box List::End)),
/home/nate/git/rust/list/src/main.rs:19         List::Node(_, ref next_node) => add(&**next_node, x),
/home/nate/git/rust/list/src/main.rs:20     }
/home/nate/git/rust/list/src/main.rs:21 }
/home/nate/git/rust/list/src/main.rs:18:16: 18:60 note: ...but borrowed value is only valid for the expression at 18:15
/home/nate/git/rust/list/src/main.rs:18         List::End => list = &*(box List::Node(x, box List::End)),
                                                             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error: aborting due to previous error
Could not compile `list`.

To learn more, run the command again with --verbose.

And the code that I'm trying to compile:

enum List {
    Node(uint, Box<List>),
    End,
}

fn main() {
    let mut list = new();

    add(&*list, 10);
    //add(list, 20);
    //add(list, 30);

    print(&*list);
}

fn add(mut list: &List, x: uint) {
    match *list {
        List::End => list = &*(box List::Node(x, box List::End)),
        List::Node(_, ref next_node) => add(&**next_node, x),
    }
}

fn new() -> Box<List> {
    box List::End
}

So why don't the boxed values live long enough? Is it because I immediately dereference them? I tried it this way:

match *list {
    List::End => {
        let end = box List::Node(x, box List::End);
        list = &*end;
    }
    List::Node(_, ref next_node) => add(&**next_node, x),
}

But I got exactly the same error. What am I missing?

2

2 Answers

9
votes

I think you’re missing some key details of Rust; there are three things that I think we need to deal with:

  1. How patterns work;
  2. The distinction between immutable (&) and mutable (&mut) references;
  3. How Rust’s ownership model works (because of your &*box attempts).

I’ll deal with the patterns part first; in fn add(mut list: &List, x: uint), there are two patterns being used, mut list and x. Other examples of patterns are the left hand side of let lhs = rhs; and the bit before the => on each branch of a match expression. How are these patterns applied to calls, effectively? It’s really like you’re doing this:

fn add(__arg_0: &List, __arg_1: uint) {
    let mut list = __arg_0;
    let x = __arg_1;
    …
}

Perhaps that way of looking at things will make it clearer; the signature of a function does not take the patterns that the variables are bound to into account at all. Your function signature is actually in canonical form fn add(&List, uint). The mut list part just means that you are binding the &List value to a mutable name; that is, you can assign a new value to the list name, but it has no effect outside the function, it’s purely a matter of the binding of a variable to a location.

Now onto the second issue: learn the distinction between immutable references (type &T, value &x) and mutable references (type &mut T, value &x). These are so fundamental that I won’t go into much detail here—they’re documented sufficiently elsewhere and you should probably read those things. Suffice it to say: if you wish to mutate something, you need &mut, not &, so your add method needs to take &mut List.

The third issue, that of ownership: in Rust, each object is owned in precisely one location; there is no garbage collection or anything, and this uniqueness of ownership means that as soon as an object passes out of scope it is destroyed. In this case, the offending expression is &*(box List::Node(x, box List::End)). You have boxed a value, but you haven’t actually stored it anywhere: you have just tried to take a reference to the value contained inside it, but the box will be immediately dropped. What you actually want in this case is to modify the contents of the List; you want to write *list = List::Node(x, box List::End), meaning “store a List::Node value inside the contents of list” instead of list = &…, meaning “assign to the variable list a new reference”.

You’ve also gone a tad overboard with the boxing of values; I’d tend to say that new() should return a List, not a Box<List>, though the question is slightly open to debate. Anyway, here’s the add method that I end up with:

fn add(list: &mut List, x: uint) {
    match *list {
        List::End => *list = List::Node(x, box List::End),
        List::Node(_, box ref mut next_node) => add(next_node, x),
    }
}

The main bit you may have difficulty with there is the pattern box ref mut next_node. The box ref mut part reads “take the value out of its box, then create a mutable reference to that value”; hence, given a Box<List>, it produces a &mut List referring to the contents of that box. Remember that patterns are completely back to front compared with normal expressions.

Finally, I would strongly recommend using impls for all of this, putting all the methods on the List type:

enum List {
    Node(uint, Box<List>),
    End,
}

impl List {
    fn new() -> List {
        List::End
    }

    fn add(&mut self, x: uint) {
        match *self {
            List::End => *self = List::Node(x, box List::End),
            List::Node(_, box ref mut next_node) => next_node.add(x),
        }
    }
}

fn main() {
    let mut list = List::new();

    list.add(10);
}
5
votes

Your attempts to fix the other compiler errors have, unfortunately, led you to a dark place of inconsistency. First you need to make up your mind whether you want a Box<List> or a List as your handle for the head of a (sub-)list. Let's go with List because that is more flexible and generally the path of least resistance.

Then, you need to realize there is a difference between mut list: &List and list: &mut List. The first is a read-only reference which you can change to point at other read-only things. The second is a read-write reference which you can not change to point at other read-write things. There's also mut list: &mut List because these two capabilities are orthogonal.

In add, when you write list = ..., you are only affecting your local variable. It has no effect on the caller. You want to change the list node the caller sees! Since we said we wanted to deal with List, not boxes, we'll change the contents of the list nodes that exist (replacing the final End with a Node(..., box End)). That is, signature and code change as follows:

fn add(list: &mut List, x: uint) {
    match *list {
        List::End => *list = List::Node(x, box List::End),
        List::Node(_, box ref mut next_node) => add(next_node, x),
    }
}

Note that *list = is different from list =, in that we now change the contents of the list node in-place instead of making our local reference point at a different node.

For consistency and ergonomics (and a tiny bit of efficiency), you should probably change new to return a bare List, i.e.:

fn new() -> List {
    List::End
}

This also saves you all the nasty reborrowing (&*) in the calls:

let list = new(); // type: List
add(&mut list, 10);

Finally, as for why the box did not live long enough: Well, you basically created a local/temporary box, took a reference to it, and then attempted to pass on the reference without keeping the box alive. A box without an owner is deallocated, so you need to give it an owner. In the fixed code above, the owner is the next field of the List::Node we create and write to the &mut List.