Lifetime on trait and mutable borrow

The following doesn't compile:

trait Foo<'a> {
    fn new_foo(buf: &'a mut [u8]) -> Self;
}

fn bar<'a, T>(buf: &'a mut [u8])
where T: Foo<'a> {
    {
        T::new_foo(buf);
    }
    {
        T::new_foo(buf);
    }
}

First one was dropped so why is it a problem if there is another mutable borrow?

1 Like

Because you explicitly told the compiler that you have an &'a mut [u8] that is borrowed for 'a, i.e., for the entirety of its validity. You declared that T: Foo<'a>. Hence, dropping that mutable reference does not end the borrow. The lifetime 'a flows into the type parameter T because it is parameterized on the lifetime 'a.

4 Likes

Is there no way to have a Foo<'a> that I can instantiate twice inside bar?

This one still doesn't work:

trait Foo<'a> {
    fn new_foo<'b>(buf: &'b mut [u8]) -> Self
    where
        'b: 'a;
}

fn bar<'a, 'b, T>(buf: &'b mut [u8])
where
    T: Foo<'a>,
    'b: 'a,
{
    {
        T::new_foo(buf);
    }
    {
        T::new_foo(buf);
    }
}

Higher-ranked trait bounds do work though:

trait Foo<'a> {
    fn new_foo(buf: &'a mut [u8]) -> Self;
}

// Compiler is happy with this
fn bar<T>(buf: &mut [u8])
where for<'a> T: Foo<'a> {
    T::new_foo(buf);
    T::new_foo(buf);
}

But that comes with the trouble of actually implementing Foo<'a> on a concrete type.

I don't get this point, what exactly is the trouble with implementing Foo<'a> on a concrete type?

Starting with this,

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

This compiles but "is not general enough" so I can't use it in bar.

impl<'a> Foo<'a> for ConcreteFoo<'a> {
    fn new_foo(buf: &'a mut [u8]) -> ConcreteFoo<'a> {
        ConcreteFoo { buf }
    }
}

Whereas the general case has a problem with lifetimes and won't compile. If I try to bound it with 'a: 'b then it's not general enough again:

impl<'a, 'b> Foo<'a> for ConcreteFoo<'b> {
    fn new_foo(buf: &'a mut [u8]) -> ConcreteFoo<'b> {
        ConcreteFoo { buf }
    }
}

Managed to find an acceptable solution using GAT.

trait Foo {
    type Output<'a>: Foo;
    fn new_foo<'a>(buf: &'a mut [u8]) -> Self::Output<'a>;
}

fn bar<T>(buf: &mut [u8])
where
    T: Foo,
{
    {
        let x = T::new_foo(buf);
    }
    {
        let y = T::new_foo(buf);
    }
}

Still have some weird quirks though:

fn bar<T>(buf: &mut [u8])
where
    T: Foo,
{
    let mut x = T::new_foo(buf);
    std::mem::drop(x); // Why is manually dropping this necessary??
    x = T::new_foo(buf);
}

EDIT:

Ugh. Spoke too soon. I ran into a compiler bug on the actual code where it completely ignores the manual mem::drop. Here's the code for anyone who's curious.

EDIT2:

So it wasn't a compiler bug. Just an interaction with a potentially Drop type. Now I'm back to square one because I'm pretty sure there isn't a way to tell the compiler to limit a trait to non-Drop types.

// Won't compile
fn bar<T>(buf: &mut [u8])
where
    T: Foo,
{
    let mut x = T::new_foo(buf);
    loop {
        std::mem::drop(x);
        x = T::new_foo(buf);
    }
}

&'a mut [u8] explicitly forbids this. You are telling the compiler to stop you from allowing more than one call ever by adding 'a to the &'a mut, and that 'a outlives Foo, so everything is locked exclusively, once and only once, for as long as 'a exists.

'a on the trait means the loan must outlive the object, and mut is exclusive, so 'a mut means only one loan ever for the whole duration of the object's lifetime.

'b: 'a; won't save you in &mut case, because exclusive lifetimes are invariant, i.e. maximally inflexible and can't be implicitly shortened or extended, so 'b: 'a basically means "'b is exactly the same thing as 'a", and you're back to square one.

Don't put (forever-long) lifetimes on &mut self, and you won't have this limitation, because then every call to &mut self will create (reborrow) a new loan that is only temporary for the duration of the call, and most importantly, not as long as the 'a.

1 Like

Your implementation seems correct to me.

Whenever you have problems of the form "won't compile", you should include the full program, and the compiler error you see.

2 Likes

Just noticed the real code here.. write your trait as so

pub trait PayloadReader<'a>
where
    Self: Sized,
{
    type Error;

    fn new_reader(output: &'a mut [u8]) -> Self;

    fn read_payload(self, payload: &[u8]) -> Result<PayloadReaderOutput<Self>, Self::Error>;
}

and use the bound

for <'b> PayloadReader<'b>

and remove all of the drops. All of your problems will be gone...


The problem is that this trait really makes no sense, so you cannot use it practically as you would expect to use it:

pub trait PayloadReader
{
    type Reader<'a>: PayloadReader;
    fn new_reader<'a>(output: &'a ...) -> Self::Reader<'a>;
    fn read_payload(self, payload: ...) -> Result<PayloadReaderOutput<Self>, Self::Error>;
}

The function <T as PayloadReader>::new_reader creates an instance of the associated type Reader but that type is totally unrelated to T! It can be any type, the compiler has no idea that you always implement it as

impl<'x> PayloadReader for MyType<'x>
{
   type Reader<'a> = MyType<'a>;
   fn new_reader<'a>(output: &'a ...) -> Self::Reader<'a>;
   fn read_payload(self : MyType<'x>, payload: ...) -> Result<PayloadReaderOutput<MyType<'x>>, Self::Error>;
}

Crucially, in the above trait, the lifetimes of new_reader and read_payload are totally unrelated lifetimes 'a and 'x. If you look at your impls, this fact should be obvious. This makes the trait basically useless.

No, it won't. That was actually my very first try at it. Let me repeat the 4th post:

trait Foo<'a> {
    fn new_foo(buf: &'a mut [u8]) -> Self;
}

fn bar<T>(buf: &mut [u8])
where
    for<'a> T: Foo<'a>,
{
    T::new_foo(buf);
    T::new_foo(buf);
}

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

// Not general enough
impl<'a> Foo<'a> for ConcreteFoo<'a> {
    fn new_foo(buf: &'a mut [u8]) -> ConcreteFoo<'a> {
        ConcreteFoo { buf }
    }
}

// The general case with incorrect lifetime bounds
impl<'a, 'b> Foo<'a> for ConcreteFoo<'b> {
    fn new_foo(buf: &'a mut [u8]) -> ConcreteFoo<'b> {
        ConcreteFoo { buf }
    }
}

// General case with *correct* lifetime bounds is not general enough
impl<'a, 'b> Foo<'a> for ConcreteFoo<'b>
where
    'a: 'b,
{
    fn new_foo(buf: &'a mut [u8]) -> ConcreteFoo<'b> {
        ConcreteFoo { buf }
    }
}

fn some_func() {
    let mut buf = vec![0u8; 8];
    bar::<ConcreteFoo>(&mut buf);
}

What I'm getting on the replies above is that I should make the lifetimes totally unrelated.

I don't think that's what is implied, though. You cannot just conjure a loan from thin air, which is what "unrelated lifetimes" would do. You may want a shared reference with interior mutability which won't have the uniqueness constraint that &'a mut T has.

Not sure what you mean by "Not general enough". That impl is actually correct. If you're really convinced that it's not, feel free to share any errors you see.

What I mean to say is, I get that I need the output of new_foo to not be bound to &'a mut [u8] which is what I attempted to do with the higher-ranked trait bound and the GAT.

That's not what the GAT approach does though. The compiler is able to deduce the lifetime properly. Bound to &'a mut [u8] that is, which is why I'm back to square one.

   Compiling playground v0.0.1 (/playground)
error: implementation of `Foo` is not general enough
  --> src/lib.rs:26:5
   |
26 |     bar::<ConcreteFoo>(&mut buf);
   |     ^^^^^^^^^^^^^^^^^^ implementation of `Foo` is not general enough
   |
   = note: `Foo<'0>` would have to be implemented for the type `ConcreteFoo<'_>`, for any lifetime `'0`...
   = note: ...but `Foo<'1>` is actually implemented for the type `ConcreteFoo<'1>`, for some specific lifetime `'1`

error: could not compile `playground` due to previous error

Playground link.

The other cases are in the comment you replied to. Just copy-paste them in.

I guess that means we agree that you cannot conjure loans.

Here's one way to relax the borrow to a shared reference with interior mutability: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=47867557dd5242c6f4f07033fe2ee549

If instead it needs to be moved across threads, then you are looking for a different shared reference and interior mutability primitive like Arc<Mutex<T>>: Rust Playground (Note also that Tokio’s flavor of Mutex can be held across await points.)

And of course, if either of these are unacceptable it might be nice having that context.

Okay sorry I missed that one. I think my previous suggestion isn't really related to what you're trying to do. It seems you want to use a single type as a provider of a parametric family of traits. i.e. the call bar::<ConcreteFoo>(..) should mean for <'a> . ConcreteFoo<'a>, but this isn't expressible in rust, it requires higher kinded types (which are types that are not inhabited by values, but rather form other types).

Note that the only reason bar::<ConcreteFoo>() even parses correctly is due to lifetime elision. It really means bar::<ConcreteFoo<'_>>(). This is what the error tells you (roughly): the elided lifetime is the "for some specific lifetime '1" portion. The compiler helpfully names the elided lifetime for you by writing ConcreteFoo<'1>. In short, there is no such thing as "the type ConcreteFoo" - you cannot even name such a thing, let alone manipulate it.

Anyways, GATs may be appropriate here, but the Self type is not meaningful to the implementation (it's just a tag type), because you only want to use it to identify a lifetime parametric-family of types. Something like this

trait PayloadReader
{
  type Error;
  type PayloadReaderImpl<'a>;

  fn new_reader<'a>(buf: &'a mut [u8]) -> Self::PayloadReaderImpl<'a>;
  fn read_payload<'a>(_ : Self::PayloadReaderImpl<'a>, payload: i32) -> Result<PayloadReaderOutput<Self::PayloadReaderImpl<'a>>, Self::Error>;
}

if you want the self reciever type ergonomics, you could write it this way

trait PayloadReaderFactory
{
  type Error;
  type PayloadReaderImpl<'a> : PayloadReader<'a, Self::Error>;

  fn new_reader<'a>(buf: &'a mut [u8]) -> Self::PayloadReaderImpl<'a>;
}

trait PayloadReader<'a, Error> : Sized
{
  fn read_payload(self, payload: i32) -> Result<PayloadReaderOutput<Self>, Error>;
}

(but this version is potentially more restrictive, if you need more methods other than read_payload)

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.