How to return reference with original lifetime from within a newtype?

I want to wrap a &'a mut Foo into a newtype and then get it out with its original lifetime without consuming the newtype. I seem to be unable to do this. I’d love any insights as to whether this is possible or whether it would be unsound to support such a thing.
(Note that I can think of workarounds for my specific needs. I’m just trying to comprehend what’s going on here.)

struct Foo;
// Newtype wrapper for `&mut Foo`.
struct FooMut<'a>(&'a mut Foo);

impl<'a> FooMut<'a> {
    // Same as `fn get_fail<'b>(&'b mut self) -> &'b mut Foo`.
    // Doesn't allow the returned value to outlive `FooMut`.
    pub fn get_fail(&mut self) -> &mut Foo { self.0 }

    // Want this, but doesn't compile:
    // "lifetime of reference outlives lifetime of borrowed content."
    //pub fn get<'b>(&'b mut self) -> &'a mut Foo { self.0 }
    
    // Works, but I don't want to consume FooMut.
    pub fn get_consume(self) -> &'a mut Foo { self.0 }
}
        
fn main() {
    let mut foo = Foo;

    // Fails with "borrowed value does not live long enough"
    // as expected.
    let _bar = FooMut(&mut foo).get_fail();

    //let _baz = FooMut(&mut foo).get();          // Wanted.
    //let _quz = FooMut(&mut foo).get_consume();  // Works.
}

(Playground)

On the definition of get(), I tried where 'a: 'b, but rust doesn’t like that kind of constraint compared to, say, 'b: 'a since 'b is the method’s type parameter.

This would be unsound as it means you could have multiple mutable aliases to the same location at once.

A mutable reference is move-only - you can’t get it out (with original lifetime) without consuming the newtype wrapper. You can reborrow for a shorter lifetime (which is what get_fail looks to be trying?). You can also borrow FooMut for 'a, but that’s fairly pointless.

1 Like

I see. So it seems like rust doesn’t treat the value returned by get() as a reborrow of FooMut unless the the lifetime of the returned reference is more explicitly linked to FooMut's. Is that correct?

I was hoping to make this link with 'a: 'b, but I guess the compiler doesn’t support that kind of variance.

Edit: To validate your comment, I tried with non-mut and indeed, the definition of get works in that case. That’s slightly confusing since the error for the mut version didn’t indicate anything about mutability at all.

If you elide the lifetime parameters, it’ll automatically use a reborrowing form. But you can’t borrow &'b mut self and return &'a mut Foo unless 'b: 'a. But that would be the same thing as:

fn get(&'a mut self) -> &'a mut Foo

And I claim this is pointless because it ends up “locking” the FooMut instance for its entire lifetime :slight_smile:. You may as well consume FooMut in that case.

Right - immutable references are Copy types so you can return the reference with original lifetime from a shorter immutable borrow of self. This can lead to aliasing, but that’s of course fine because that’s what immutable references allow/support.

This is why it’s best to think of &T as an aliasing/shared borrow and &mut T as a unique/exclusive borrow. The “immutability” (or “mutability”) part is kind of a red herring because &T can easily allow (interior) mutability - the key difference is in the aliasing guarantees/semantics. Of course we all mostly talk about them as immutable/mutable borrows in the vernacular, but that’s not really the best frame of mind.

1 Like

Yea, I was trying to see if there's a reborrowing version. But now, it's clearer that one probably wants two methods for two different use-cases:

  • a consuming one (for reborrowing for a longer lifetime than the FooMut'), and
  • a borrowing one for the case that one wants to use FooMut afterward.

See also:

1 Like