Lifetime error with mutable member struct

Here is the code:

struct Buffer<'a> {
	buf: &'a mut Vec<u8>,
	pos: usize,
}

impl<'a> Buffer<'a> {
	fn new(v: &mut Vec<u8>) -> Buffer {
		Buffer { buf: v, pos: 0 }
	}

	fn read_bytes(&mut self) -> &'a [u8] {
		self.pos += 3;
		&self.buf[self.pos - 3..self.pos]
	}
}

fn print(b1: &[u8], b2: &[u8]) {
	println!("{:#?} {:#?}", b1, b2)
}

fn main() {
	let mut v = vec![1, 2, 3, 4, 5, 6];
	let mut buf = Buffer::new(&mut v);
	let b1 = buf.read_bytes();
}
//

Then I got a compile error:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
   --> src/main.rs:406:4
    |
406 |         &self.buf[self.pos - 3..self.pos]
    |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
note: first, the lifetime cannot outlive the anonymous lifetime defined here...
   --> src/main.rs:403:16
    |
403 |     fn read_bytes(&mut self) -> &'a [u8] {
    |                   ^^^^^^^^^
note: ...so that reference does not outlive borrowed content
   --> src/main.rs:406:4
    |
406 |         &self.buf[self.pos - 3..self.pos]
    |          ^^^^^^^^
note: but, the lifetime must be valid for the lifetime `'a` as defined here...
   --> src/main.rs:398:6
    |
398 | impl<'a> Buffer<'a> {
    |      ^^
note: ...so that reference does not outlive borrowed content
   --> src/main.rs:406:3
    |
406 |         &self.buf[self.pos - 3..self.pos]
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
For more information about this error, try `rustc --explain E0495`.

However, when I tried to make buf immutable, the compiler did't report the error anymore, like this:

struct Buffer<'a> {
	buf: &'a Vec<u8>,
	pos: usize,
}

impl<'a> Buffer<'a> {
	fn new(v: &mut Vec<u8>) -> Buffer {
		Buffer { buf: v, pos: 0 }
	}

	fn read_bytes(&mut self) -> &'a [u8] {
		self.pos += 3;
		&self.buf[self.pos - 3..self.pos]
	}
}

fn print(b1: &[u8], b2: &[u8]) {
	println!("{:#?} {:#?}", b1, b2)
}

fn main() {
	let mut v = vec![1, 2, 3, 4, 5, 6];
	let mut buf = Buffer::new(&mut v);
	let b1 = buf.read_bytes();
}

According to the lifetime elision rules in rust-lang book:

The compiler uses three rules to figure out what lifetimes references have when there aren’t explicit annotations. The first rule applies to input lifetimes, and the second and third rules apply to output lifetimes. If the compiler gets to the end of the three rules and there are still references for which it can’t figure out lifetimes, the compiler will stop with an error. These rules apply to fn definitions as well as impl blocks.

The first rule is that each parameter that is a reference gets its own lifetime parameter. In other words, a function with one parameter gets one lifetime parameter: fn foo<'a>(x: &'a i32); a function with two parameters gets two separate lifetime parameters: fn foo<'a, 'b>(x: &'a i32, y: &'b i32); and so on.

The second rule is if there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters: fn foo<'a>(x: &'a i32) -> &'a i32.

The third rule is if there are multiple input lifetime parameters, but one of them is &self or &mut self because this is a method, the lifetime of self is assigned to all output lifetime parameters. This third rule makes methods much nicer to read and write because fewer symbols are necessary.

I can't tell what's the difference here. IMOP both lifetime elision should work. Can anyone HELP me. Thanks a lot.

This will work:

impl<'a> Buffer<'a> {
	fn new(v: &mut Vec<u8>) -> Buffer {
		Buffer { buf: v, pos: 0 }
	}

	fn read_bytes(&mut self) -> &[u8] {
		self.pos += 3;
		&self.buf[self.pos - 3..self.pos]
	}
}

Lifetime elision-vise, this is equivalent to:

impl<'a> Buffer<'a> {
	fn new(v: &mut Vec<u8>) -> Buffer {
		Buffer { buf: v, pos: 0 }
	}

	fn read_bytes<'b>(&'b mut self) -> &'b [u8] {
		self.pos += 3;
		&self.buf[self.pos - 3..self.pos]
	}
}
1 Like

Yeah. I can fix it by explicit lifetime annotations. But I really can't understand what's the difference between these two versions.

Your code

// first version (elided), fails
impl<'a> Buffer<'a> {
	fn read_bytes(&mut self) -> &'a [u8] {
		self.pos += 3;
		&self.buf[self.pos - 3..self.pos]
	}
}

is equivalent to

// first version (explicit), fails in the same way
impl<'a> Buffer<'a> {
	fn read_bytes<'b>(&'b mut self) -> &'a [u8] {
		self.pos += 3;
		&self.buf[self.pos - 3..self.pos]
	}
}

which is different from

// second version (elided), compiles successfully
impl<'a> Buffer<'a> {
	fn read_bytes(&mut self) -> &[u8] {
		self.pos += 3;
		&self.buf[self.pos - 3..self.pos]
	}
}

which would be equivalent to

// second version (explicit), compiles successfully
impl<'a> Buffer<'a> {
	fn read_bytes<'b>(&'b mut self) -> &'b [u8] {
		self.pos += 3;
		&self.buf[self.pos - 3..self.pos]
	}
}

The lifetime elision works, however borrow checking (of the function body) fails if the return type is &'a [u8]. Perhaps the error message “cannot infer an appropriate lifetime for lifetime parameter” makes you thing this is about lifetime elision?

You're right. My earlier expression is problematic. The correct question is Why these two lifetime elision results are different.
IMHO, according to the third lifetime elision rule, the lifetime of self is assigned to all output lifetime parameters, the two versions should be both equivalent to:

impl<'a> Buffer<'a> {
	fn read_bytes<'b>(&'b mut self) -> &'b [u8] {
		self.pos += 3;
		&self.buf[self.pos - 3..self.pos]
	}
}

Is there some implicit conditions to apply the third rule? I'm just a newbie rustacean, sorry about the poor expression. :joy:

Actually, there’s a fairly explicit condition, namely the first sentence: “The compiler uses three rules to figure out what lifetimes references have when there aren’t explicit annotations.” This means that only those lifetimes that are not annotated explicitly are given by elision. So the elision rules can only modify

// first version (elided), fails
impl<'a> Buffer<'a> {
	fn read_bytes(&mut self) -> &'a [u8] {
		self.pos += 3;
		&self.buf[self.pos - 3..self.pos]
	}
}

by adding extra parameters

// first version (elided), fails
impl<'a> Buffer<'a> {
	fn read_bytes</* HERE */>(&mut self) -> &'a [u8] {
		self.pos += 3;
		&self.buf[self.pos - 3..self.pos]
	}
}

and filling in missing/elided lifetimes

// first version (elided), fails
impl<'a> Buffer<'a> {
	fn read_bytes(&/* HERE */ mut self) -> &'a [u8] {
		self.pos += 3;
		&self.buf[self.pos - 3..self.pos]
	}
}

(there’s only one elided lifetime in this example).

Hence

impl<'a> Buffer<'a> {
	fn read_bytes<'b>(&'b mut self) -> &'b [u8] {
		self.pos += 3;
		&self.buf[self.pos - 3..self.pos]
	}
}

is not a possible result because the return type no longer has the type &'a [u8] with the lifetime 'a from the outer impl<'a> Buffer<'a>.

The only question could be: Why

impl<'a> Buffer<'a> {
	fn read_bytes<'b>(&'b mut self) -> &'a [u8] {
		self.pos += 3;
		&self.buf[self.pos - 3..self.pos]
	}
}

and not

impl<'a> Buffer<'a> {
	fn read_bytes(&'a mut self) -> &'a [u8] {
		self.pos += 3;
		&self.buf[self.pos - 3..self.pos]
	}
}

but the answer here is: the first rule says that every elided lifetime in the function inputs “gets its own lifetime parameter”, which means each gets its own freshly introduced lifetime parameter. No way of using the already existing 'a here.


No problem, learning the right terminology to ask good question is an acquired skill, too.

You're missing the important point. Quoting the reference (emphasis mine):

In you case, output lifetime is not elided - it is specified explicitly, and it would be really bad if Rust was allowed to override it.

1 Like

By the way, the original second version should be

// second version (elided), compiles successfully
impl<'a> Buffer<'a> {
	fn read_bytes(&mut self) -> &'a [u8] {
		self.pos += 3;
		&self.buf[self.pos - 3..self.pos]
	}
}

but not

// second version (elided), compiles successfully
impl<'a> Buffer<'a> {
	fn read_bytes(&mut self) -> &[u8] {
		self.pos += 3;
		&self.buf[self.pos - 3..self.pos]
	}
}

So maybe is equivalent to this?

// second version (elided), compiles successfully
impl<'a> Buffer<'a> {
	fn read_bytes(&'a mut self) -> &'a [u8] {
		self.pos += 3;
		&self.buf[self.pos - 3..self.pos]
	}
}

This is unnecessarily restrictive. In general, &'a mut T<'a> is a huge antipattern and almost never the thing you want, since it forces the lifetime of &mut self to be as long as lifetime of self, so you won't be able to do anything with Buffer after calling this method - it'll be considered still borrowed.

I see. But both two version's output lifetime are not elided :joy:

Isn’t that exactly the same as what I already called “first version”?


Unless I’m misinterpreting your, I think I answered this question above:

Sorry, I thought the second version you said is my second version, which is:

struct Buffer<'a> {
	buf: &'a Vec<u8>,
	pos: usize,
}

impl<'a> Buffer<'a> {
	fn new(v: &mut Vec<u8>) -> Buffer {
		Buffer { buf: v, pos: 0 }
	}

	fn read_bytes(&mut self) -> &'a [u8] {
		self.pos += 3;
		&self.buf[self.pos - 3..self.pos]
	}
}

fn print(b1: &[u8], b2: &[u8]) {
	println!("{:#?} {:#?}", b1, b2)
}

fn main() {
	let mut v = vec![1, 2, 3, 4, 5, 6];
	let mut buf = Buffer::new(&mut v);
	let b1 = buf.read_bytes();
}

What you said above help me a lot.

But I don't know If you have noticed that the only difference between two pieces of code in my question is the struct member buf, one is mutable, the other is immutable:

// first version, compile fail
struct Buffer<'a> {
	buf: &'a mut Vec<u8>,
	pos: usize,
}

and

// second version, compile succeed
struct Buffer<'a> {
	buf: &'a Vec<u8>,
	pos: usize,
}

Ah, no... could’ve been more explicit; I was just giving arbitrary names “first version” and “second version” to the versions. One is your code, the other one from @alice.

I thought your “really can't understand what's the difference between these two versions” was referring to your code compared to the “version” with explicit lifetime annotations.


I did notice. That’s why I didn’t discuss those two versions because the difference between those two struct definitions is irrelevant for understanding the behavior of lifetime elision. They make a difference for type checking and borrow checking only.

Could you tell more details about the difference and why the code with buf: &'a mut Vec<u8> cause a lifetime error ?

Sure. The function you’re trying to implement is

fn read_bytes<'b>(self: &'b mut Buffer<'a>) -> &'a [u8] { … }

where Buffer<'a> contains a &'a mut Vec<u8>.

Now, the caller of such a function could borrow a buffer: Buffer<'a> for a short time (assumg 'b is a much shorter lifetime than 'a in the read_bytes call) to obtain a &'a [u8] pointing into the contained buffer. But after that call, after the borrow of buffer ended, it could get mutable access to the Vec while the immutable reference into the same Vec is still usable; violating the exclusive access that mutable references promise.

fn main() { // this compiles!
    let mut v = vec![1, 2, 3, 4, 5, 6];
    let mut buf = Buffer::new(&mut v);
    let b1: &[u8] = buf.read_bytes();
    let buffer: &mut [u8] = buf.buf;
    // now we can access *both* b1 and buffer, violating Rust's borrowing rule
    println!("{}", b1[0]);
    buffer[0] += 1;
    println!("{}", b1[0]);
}

(playground)

This demonstrates why a function like read_bytes cannot be implemented soundly (in a non-trivial manner, i.e. in a way that it actually returns a reference into buf); otherwise this use-case would violate Rust’s ownership+borrowing rules, in particular the rule that mutable references are exclusive.

Now, with a buf: &'a Vec<u8> field, all the above code could achieve is two aliasing immutable references, which is totally fine.

I’m not explaining here how exactly the borrow checker can figure out that the read_bytes function implementation is unsound; I’m just demonstrating that it rightfully rejects the code.


There’s also the option to use a &'a [u8] field, and you wouldn’t even need a pos necessarily:

struct Buffer<'a> {
	buf: &'a [u8],
}
impl<'a> Buffer<'a> {
	fn read_bytes(&mut self) -> &'a [u8] {
		let (bytes, new_buf) = self.buf.split_at(3);
		self.buf = new_buf;
		bytes
	}
}

(playground)

and this one could even be turned into a mutable version:

struct Buffer<'a> {
	buf: &'a mut [u8],
}
impl<'a> Buffer<'a> {
	fn read_bytes(&mut self) -> &'a mut [u8] {
	    let buf = std::mem::take(&mut self.buf);
		let (bytes, new_buf) = buf.split_at_mut(3);
		self.buf = new_buf;
		bytes
	}
}

(playground)

This is sound because now, the call to read_bytes will make the returned slice of bytes unaccessible through the Buffer. Of course, there might be other kinds of API that only a Buffer with &'a mut Vec<u8> can support.

1 Like

I got it !! Thanks very much for your very careful, patient and comprehensive answer.
P.S. the code I mentioned has no practical use, just a case to study rust lifetime. I really gained a lot through your reply. Thanks it again !! :smiley_cat:

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.