Search code examples
genericsrusttraitslifetime

Lifetime issue in default trait implementation


I'm trying to make a generic trait where one of the methods uses a default implementation that wraps the rest to reduce boilerplate.

Here's a minimal reproduction:

trait ReadFormat<'a, 'b: 'a, Arg: 'b, T: 'static>: Sized {
    fn new(c: &'a mut Cursor<'a>, arg: Arg) -> Self;
    fn into_format(self) -> SResult<T>;
    fn read(bytes: &'b [u8], arg: Arg) -> SResult<T> {
        let c = &mut Cursor::new(bytes);
        let r = Self::new(c, arg);
        let f = r.into_format();
        f
    }
}

I'm getting the following error

61 | trait ReadFormat<'a, 'b: 'a, Arg: 'b, T: 'static>: Sized {
   |                  -- lifetime `'a` defined here
...
65 |         let c = &mut Cursor::new(bytes);
   |                      ^^^^^^^^^^^^^^^^^^ creates a temporary value which is freed while still in use
66 |         let r = Self::new(c, arg);
   |                 ----------------- argument requires that borrow lasts for `'a`
...
69 |     }
   |     - temporary value is freed at the end of this statement

I've tried various combinations of lifetimes, but none of them work. In general, I don't understand why it expects Self to live after read returns when it's consumed by into_format and T owns all of it's data.

If I copy-paste the exact same code into a concrete implementation and replace Self::new with Reader::new it compiles.

impl<'a, 'b: 'a> ReadFormat<'a, 'b, (), ()> for Reader<'a> {
    ..
    fn read(bytes: &'b [u8], arg: ()) -> SResult<()> {
        let c = &mut Cursor::new(bytes);
        let r = Reader::new(c, arg);
        let f = r.into_format();
        f
    }
}

Solution

  • I don't understand why it expects Self to live after read returns

    It doesn't; the problem is with c how long c lives.

    The signature of new() expects a reference that lives for 'a, which is a lifetime parameter declared on the trait. Therefore, for any specific usage of the trait, if you call new(), the c reference must be valid up until you stop using the trait implementation. But, c is a local variable in read(), which is inside the trait implementation, so it doesn't live that long.

    Here is a non-trait example of the same mistake, which fails for the same reason:

    struct Cursor {}
    struct Foo<'a> {
        c: &'a mut Cursor,
    }
    
    impl<'a> Foo<'a> {
        fn read() {
            let c = &mut Cursor {};
            let _ = Self { c };
        }
    }
    

    In order to avoid this problem, you must either

    • not have any lifetime parameters on the trait, or
    • don't make read() a trait function — that allows it to have a more flexible relationship to the involved lifetimes.

    Another problem you have is that the type &'a mut Cursor<'a> — or more generally, &'a mut Something<'a> — is essentially always wrong. Using the same lifetime for a mutable reference and for the contents of the reference creates two outlives constraints:

    • &'a mut Cursor<'a> must be valid for (must outlive) 'a, or it would not be an &'a ....
    • 'a must outlive Cursor<'a>, or the Cursors could have dangling references in them.

    The consequence of using the same lifetime in both places is that the Cursor becomes borrowed for the rest of its existence, which is probably not what you want.

    The solution is to use separate lifetimes for the reference and the referent.


    Putting all this together:

    struct Cursor<'b>(&'b [u8]);
    impl<'b> Cursor<'b> {
        fn new(bytes: &'b [u8]) -> Self {
            Self(bytes)
        }
    }
    
    trait ReadFormat<'cur, 'bytes, Arg, T>: Sized {
        fn new(c: &'cur mut Cursor<'bytes>, arg: Arg) -> Self;
        fn into_format(self) -> Result<T, ()>;
    }
    
    fn read<'b, R, Arg, T>(bytes: &'b [u8], arg: Arg) -> Result<T, ()>
    where
        // for<'cur> means this trait can be used with ANY cursor lifetime
        R: for<'cur> ReadFormat<'cur, 'b, Arg, T>
    {
        let c = &mut Cursor::new(bytes);
        let r = R::new(c, arg);
        r.into_format()
    }
    

    You can simplify things further if you make ReadFormat::new() take an owned Cursor instead of an &mut reference; that would eliminate the need for a higher-rank trait bound for the cursor lifetime, by not having a cursor lifetime at all. But maybe that doesn't serve your goals.


    By the way, a couple of hints for the next time you're asking a question or even just troubleshooting:

    • Your "minimal reproduction" is not complete; it didn't give a definition for the Cursor type. That didn't seem to matter here, but it can.
    • In "… the following error", you cut off the actual primary error message — the line that starts with error: .... It's important to include that message, and even more important to read that message. The messages on following lines are meant to be understood as further details after the primary message, not stand on their own.