Mutability affects lifetime inference


#1

Consider:

  • a struct with a borrowed reference field;
  • a function taking a reference to this struct and returning a reference borrowed from the inner field.

This does actually compile:

struct B<'a> {
    buf: &'a [u8],
}

fn foo<'a>(b: &B<'a>) -> &'a [u8] {
    &b.buf[..]
}

However if the reference within the struct is mutable, so the function can first update b.buf:

struct B<'a> {
    buf: &'a mut [u8],
}

… then the compiler says:

src/lib.rs:6:6: 6:15 error: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements [E0495]
src/lib.rs:6     &b.buf[..]
                  ^~~~~~~~~

I can’t quite put my head around the reason why is this? Why mutability changes anything about b.buf having the lifetime 'a and foo<'a> returning a reference with the same lifetime?

P.S. This is a distilled example. The real world task is to have a parser with a caller-supplied buffer returning parsed slices from that buffer.


[workaround found] "cannot infer an appropriate lifetime" error borrowing from a mutable field
#2

I don’t think assigning mutability through the type like that is correct. You define the type with references or types and when you construct each different type, then you detail mutability. Like so:

// No mut here.
struct B<'a> { buf: &'a [u8] }

// Like this
let mut array = [1, 2];
let b = B { buf: &mut array };

Now, I don’t think mutably borrowed arrays like that are terribly useful because it seems the compiler will block you from mutating it because there are 2 separate references to the same location which it doesn’t allow.

So, I don’t know how to get you to what you want. Someone else probably will though.


#3

The compiler also suggests to use fn foo<'a>(b: &'a B<'a>) -> &'a [u8] which compiles.

I think it can be explained like this: the borrow of B needs to live as long as the borrow of the array, since that guarantees that nobody exchanges what b.buf points to while the return value of foo lives.


#4

I’m pretty sure both previous answers are incorrect.

Both &'a mut [u8] and &'a [u8] guarantee that the memory pointed to won’t be deallocated before the end of 'a. But only the immutable reference guarantees that the memory won’t be mutated until then, making it safe to vend a new immutable reference. Therefore, you could write

fn foo<'a>(x: &'a [u8]) -> &'a [u8]

but the mutable version would be unsafe:

fn foo<'a>(x: &'a mut [u8]) -> &'a [u8]

– Oops, this is obviously wrong. Both signatures would work in that case. I’m dumb.

In the real signature, though, you have an additional guarantee: the moral equivalent of & &'a mut [u8] (if you look through the struct). Having an immutable reference to a mutable reference does guarantee that the contents of the inner reference won’t actually be mutated (because mutable references are supposed to be unique, so it wouldn’t be possible to take a unique mutable reference through a shared immutable reference)… but that holds only as long as the lifetime of the outer reference. In this case, since you didn’t specify a lifetime, it is only guaranteed to last until the end of the function call to foo: so you can use &b.buf[..] within the function, but after that, the guarantee no longer applies, so you can’t hand it back to the caller.

(This is different from b.buf being changed to another pointer, which wouldn’t be a problem because these are not owning references.)

The solution depends on your goal, but as @birkenfeld says, one working signature is

fn foo<'a>(b: &'a B<'a>) -> &'a [u8] 

in which case the outer reference’s lifetime also must last as long as 'a. An alternative is

fn foo<'a, 'b: 'a>(b: &'b B<'a>) -> &'b [u8]

which doesn’t place constraints on the outer reference’s lifetime, but limits the lifetime of the returned reference to whatever it happens to be.


#5

I don’t think so. As long as the immutable reference created by foo lives, you cannot mutate the original array since it’s borrowed, so making a &A from a &mut A is perfectly safe.


#6

I knew that :cold_sweat:

…but somehow I ended up saying something completely dumb anyway. Oh well. Imagine I wrote &RefCell<&'a mut [u8]> or something, where you have temporary access to a mutable (vs. immutable) reference. Except that would be a highly overcomplicated example.


#7

@comex: Is it valid to annotate a type with mutability at the struct definition? That’s all I was commenting about. Doesn’t seem like something useful. mut seems relevant to usage, not to type layout.

// Is `mut` valid here?
struct B<'a>(&mut 'a i32)

#8
struct B<'a>(&'a i32);

fn main() {
    let mut x = 0;
    let mut b = B(&mut x);
    *b.0 = 1; // error: cannot assign to immutable borrowed content `*b.0`
}

#9

Yeah, that’s what I would have done as well. comex stated I was incorrect though so I was seeking clarification.


#10

I just don’t see how you would consider mut redundant when a shared and a mutable references are different types with different purposes.


#11

Well, the book seems to affirm what I was thinking. Also, I don’t really understand what you’re asking enough to form a proper reply otherwise…


#12
struct Point {
    x: i32,
    mut y: i32, // nope
}

So the book says that making a field mutable doesn’t make sense.

struct Borrow<'a> {
    x: &'a i32,
    y: mut &'a i32, // nope
    z: &'a mut i32, // yes
}

Neither x nor z are marked as mutable. One of them is a “mutable” (i.e. not “shared”) reference.


#13

Ah, I see. I was mistaken so &mut T is a valid type which is distinct from &T. I didn’t know that and the docs don’t seem to clarify it either. I’ll file an issue.


#14

I think I’ve figured out what’s happening there… First of all, sorry for misleading you, as the real code does indeed pass B into foo as a mutable reference — because otherwise b.buf can’t be mutated. So, both the struct and the field has to be mutable.

So my best explanation of the phenomena is this:

  • foo<'a>(b: &mut B<'a>) -> &'a [u8] can’t actually infer the lifetime of its argument. The fact that B holds an &'a reference doesn’t mean that B has the same lifetime.
  • When the field reference is immutable and the return value borrows from it, the compiler simply doesn’t care of the lifetime of the struct, it’s irrelevant
  • However when the field reference is mutable the compiler has to know the lifetime of the struct so it can make sure that the struct itself is still mutable when it returns the mutable reference to its field — hence the error, as the compiler can’t infer the lifetime of its argument.

My original mistake was thinking that the compiler can infer the struct lifetime in the immutable case and can’t otherwise. In reality it can’t, but in the immutable case it simply doesn’t matter.

I wish someone could confirm if this hypothesis has any bearing to how Rust actually works.