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


#1

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.


#2

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.


#3

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.


#4

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.


#5

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.

#6

See also: