Why are lifetimes a problem only when you take a reference to a mutable reference?

I have the following Rust code, which compiles fine:

struct T {
    x: String,
}

fn ret_x<'a, 'b>(c: &'a mut &'b T) -> &'b str {
    return &c.x;
}

However, if I have:

struct T {
    x: String,
}

fn ret_x<'a, 'b>(c: &'a mut &'b mut T) -> &'b str {
    return &c.x;
}

Then it gives me an error about lifetimes:

function was supposed to return data with lifetime `'b` but it is returning data with lifetime `'a`

What's the difference between these two cases?

Is this somehow due to reborrowing? When the "inner" reference is immutable, Rust can just copy the reference without borrowing from c itself, whereas for the case where the "inner" reference is mutable, it needs to borrow from c?

1 Like

When the "inner" reference is immutable, Rust can just copy the reference without borrowing from c itself, whereas for the case where the "inner" reference is mutable, it needs to borrow from c?

Exactly, yes. If it didn't enforce this rule, then you could end up still having the &'b T while the &'a mut ... is being used to modify the T.

Another way mutable references are more restrictive, which comes up a lot, is that they're invariant in their referent type, but variance isn't necessary to explain what's going on here.

6 Likes

Afaik this is because lifetimes for shared references are covariant, and lifetimes for unique (mutable) references are invariant.

Nvm., @kpreid just said that variance is not what explains this.

Indeed, the same error occurs for an argument of type &'a &'b mut T (wherein both lifetimes are covariant).

Beyond the discussion of the rules that would lead the compiler to reject this function, there’s also another easy argument why it must reject this function. If it didn’t, you could cause a lot of trouble as follows:[1]

struct T {
    x: String,
}

fn ret_x<'a, 'b>(c: &'a mut &'b mut T) -> &'b str {
    todo!()
}

// returns aliased mutable reference, no good ;-)
fn this_is_bad<'a, 'b>(c: &'a mut &'b mut T) -> (&'b str, &'a mut str) {
    (ret_x(c), &mut c.x)
}

Rust Playground

Since this_is_bad is allowed, ret_x (if properly implemented, not left as todo) must be rejected.


  1. I guess @kpreid already hinted at this problem… here’s a concrete code example though ↩︎

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.