Lifetime elision in struct impl


#1

I’m getting started with Rust. The language is very interesting, but the learning curve is definitely steep :slightly_smiling: . I’ve jumped straight in to write some code, and have had some trouble with getting it to compile.

I’m writing a parser that operates on a Vec buffer and I want to return chunks of data (the MySlice structure), each chunk being essentially a slice in the buffer. Originally the code did not require lifetime specifiers in structures, I added them to be able to describe the data: &'a [u8] field. At this point I had the following code:

struct MyParser<'a> {
    buffer: &'a Vec<u8>,
    num_modifs: u32,
}

struct MySlice<'a> {
    data: &'a [u8],
}

impl<'a> MyParser<'a> {
    fn new(buf: &'a Vec<u8>) -> MyParser<'a> {
        MyParser {
            buffer: buf,
            num_modifs: 0,
        }
    }

    fn test(&mut self) -> MySlice {
        let modifs = self.num_modifs;
        let res = self.do_something();
        self.num_modifs = modifs;
        res
    }

    fn do_something(&mut self) -> MySlice {
        self.inc_modifs();
        MySlice { data: &self.buffer[0..2] }
    }

    fn inc_modifs(&mut self) {
        self.num_modifs += 1;
    }
}

And when compiling, I got the following error message:

error: cannot assign to `self.num_modifs` because it is borrowed 
note: borrow of `self.num_modifs` occurs here
let res = self.do_something();

It turns out I had forgotten to specify the 'a lifetime on the result of the “do_something” and “test” methods. What I don’t understand is why does the compiler tell me that I’m borrowing self.num_modifs if I don’t specify the lifetime? What’s going on?

EDIT: replaced the preformatted block by a code block using 3 backticks to get syntax highlighting


#2

Any reference is a borrow:

let res = self.do_something();

Check the signature from do_something():

fn do_something(&mut self)

It takes &mut self, which means that it borrows self.


#3

Except that it compiles if I correct the return type of the do_something method to MySlice<'a>. Why? What is the difference between the two, and which one is correct? (if any, otherwise how to write this correctly?)


#4

There’s a lot of detail that can be stripped out of this, while still illustrating the same behavior.

If I have a function with a signature like this:

fn f(p: &mut i32) -> i32 { ... }

then filling in the implicit lifetimes gives me:

fn f<'a>(p: &'a mut i32) -> i32 { ... }

If I call f like this:

let mut x : i32 = 9;
let r = f(&mut x);

then the borrow checker concludes that the mutable borrow of x ends when f returns: f takes no other arguments with lifetime 'a, so the borrow checker can tell I didn’t stash the reference anywhere. (Mutable global variables are unsafe in Rust, so if f stored the reference in one of those, that’d be my problem, not the borrow checker’s.)

But if I have a function like this:

fn g(p: &mut i32) -> &mut i32 { ... }

filling in the implicit lifetimes means that this is actually:

fn g<'a>(p: &'a mut i32) -> &'a mut i32 { ... }

If I call g like this:

let mut x : i32 = 9;
let r = g(&mut x);

then, without ever looking at the body of g, the borrow checker concludes that g's return value must be either the reference we passed in, or something reachable from that. From this, it concludes that the borrow of x must continue after the call to g has returned: at least as far as r's scope extends, and possibly further if I do other things with r.

This is why the following is okay:

let mut x : i32 = 9;
{
    let r = g(&mut x);
    ... use r ....
}
println!("{}", x);

The mutable borrow of x flows into g, back out to r, and then ends at the closing brace—in time for us to apply println! to it.


#5

Thanks for the explanations, but it doesn’t help me to answer my original question. Let me try to come up with a simpler example then:

struct MySlice<'a> {
    data: &'a i32,
}

fn f<'a>(a: &'a i32, b: &'a i32) -> MySlice<'a> {
	MySlice {data: b}
}

fn g<'a>(a: &'a i32, b: &'a i32) -> MySlice {
	MySlice {data: b}
}

As expected, it fails to compile with:

 src/main.rs:109:37: 109:44 error: wrong number of lifetime parameters: expected 1, found 0 [E0107]
src/main.rs:109 fn g<'a>(a: &'a i32, b: &'a i32) -> MySlice {

This is the message I expected to see with the functions in my impl. And incidentally, when this error is fixed (by adding the missing lifetime argument to MySlice), the code in my original post compiles. Which brings me back to my question: why don’t I see this message with my original code?


#6

The lifetime elision rule will turn

fn do_something(&mut self) -> MySlice

into

fn do_something<'b>(&'b mut self) -> MySlice<'b>

So it works the same way jimb explained.

In your example function g, lifetime elision does not apply, so this is why the explicit lifetime for MySlice is needed. Lifetime elision does not apply for two reasons 1) There are two input references: arguments a and b. Elision requires that either there’s only one input, or that one of the inputs is self. 2) inputs already have explicit lifetime parameters.


#7

Thank you, this is what I was missing: that the elided lifetime “becomes a distinct lifetime parameter”, meaning 'b, not 'a. Which corresponds indeed to my intent: I want the lifetime of MyParser.buffer and MySlice.data to be the same as the “buf” parameter in MyParser::new, but I don’t need nor want to constrain the lifetime of MyParser itself to that.

I am under the impression that once I get past the initial obstacles (the usual suspects, aka borrow checker, lifetime, etc.), I’m going to like the language more and more :slightly_smiling: