#Lifetime issue (short minimal reproducer)

38 messages Β· Page 1 of 1 (latest)

opaque crater
#

?play

use std::{cell::RefCell, marker::PhantomData, slice::Iter};

#[derive(Default)]
struct Server<O> {
    phantom_o: PhantomData<fn() -> O>,
}

impl<O> Server<O> {
    fn oops(&self, _o: O) {}
}

#[tokio::main]
async fn main() {
    let server = Server::<IterWrapper<Iter<usize>>> {
        phantom_o: Default::default(),
    };
    tokio::spawn(async move {
        let v = vec![];
        let x = IterWrapper::new(v.iter());
        server.oops(x);
    });
}

pub struct IterWrapper<I>(RefCell<I>);
impl<I> IterWrapper<I> {
    pub fn new(iterator: I) -> Self {
        Self(RefCell::new(iterator))
    }
}
astral roverBOT
#
error[E0597]: `v` does not live long enough
  --> src/main.rs:19:34
   |
14 |     let server = Server::<IterWrapper<Iter<usize>>> {
   |         ------ lifetime `'1` appears in the type of `server`
...
19 |         let x = IterWrapper::new(v.iter());
   |                 -----------------^^^^^^^^-
   |                 |                |
   |                 |                borrowed value does not live long enough
   |                 argument requires that `v` is borrowed for `'1`
20 |         server.oops(x);
21 |     });
   |     - `v` dropped here while still borrowed

For more information about this error, try `rustc --explain E0597`.```
opaque crater
#

Does anybody know what's the problem here?
Removing the RefCell from the IterWrapper makes things compile.
It seems like there's some weird interaction between Tokio, RefCell, and the PhantomData...?

pallid jungle
#

Yeah,it looks like variance is at play here which is why it compiles when you remove RefCell since return types from functions are I believe covariant (same as shared references) - and your PhantomData is stating that O is covariant

RefCell has interior mutability, so like &mut T it's invariant from what I understand, and so can't be shortened so causes lifetime issues

Removing RefCell, we can reintroduce invariance by changing the PhantomData on Server so this shows that variance is at play causing the issue

#

?play

use std::{cell::RefCell, marker::PhantomData, slice::Iter};

#[derive(Default)]
struct Server<O> {
    phantom_o: PhantomData<fn(O)>,
}

impl<O> Server<O> {
    fn oops(&self, _o: O) {}
}

#[tokio::main]
async fn main() {
    let server = Server::<IterWrapper<Iter<usize>>> {
        phantom_o: Default::default(),
    };
    tokio::spawn(async move {
        let v = vec![];
        let x = IterWrapper::new(v.iter());
        server.oops(x);
    });
}

pub struct IterWrapper<I>(I);
impl<I> IterWrapper<I> {
    pub fn new(iterator: I) -> Self {
        Self(iterator)
    }
}
astral roverBOT
#
warning: unused import: `cell::RefCell`
 --> src/main.rs:1:11
  |
1 | use std::{cell::RefCell, marker::PhantomData, slice::Iter};
  |           ^^^^^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

error[E0597]: `v` does not live long enough
  --> src/main.rs:19:34
   |
14 |     let server = Server::<IterWrapper<Iter<usize>>> {
   |         ------ lifetime `'1` appears in the type of `server`
...
19 |         let x = IterWrapper::new(v.iter());
   |                                  ^^^^^^^^ borrowed value does not live long enough
20 |         server.oops(x);
   |         -------------- argument requires that `v` is borrowed for `'1`
21 |     });
   |     - `v` dropped here while still borrowed

For more information about this error, try `rustc --explain E0597`.```
opaque crater
#

Right, thank you for the explanation

#

@pallid jungle For a given instance of Server, I need oops to always take in the same type (which is why I used phantomdata).

The RefCell in the wrapper is because I need to serialize this iterator (which requires interior mutability since Serde only takes an immutable reference).

#

Would you happen to have an idea how to make this all work out? Doesn't have to be the same arhitecture as long as the requirements are met.

pallid jungle
#

Well it works if you don't have an iterator over borrows to the vec elements but instead just have the IterWrapper take ownership

#

?play

use std::{cell::RefCell, marker::PhantomData, slice::Iter};

#[derive(Default)]
struct Server<O> {
    phantom_o: PhantomData<O>,
}

impl<O> Server<O> {
    fn oops(&self, _o: O) {}
}

#[tokio::main]
async fn main() {
    let server = Server {
        phantom_o: Default::default(),
    };
    tokio::spawn(async move {
        let v: Vec<usize> = vec![];
        let x = IterWrapper::new(v.into_iter());
        server.oops(x);
    });
}

pub struct IterWrapper<I>(RefCell<I>);
impl<I> IterWrapper<I> {
    pub fn new(iterator: I) -> Self {
        Self(RefCell::new(iterator))
    }
}
astral roverBOT
opaque crater
#

@pallid jungle That is not possible because in my case I only need to serialize a small portion of the whole vec. (In fact, the vec is actually part of the server's state)

#

but thanks

#

To be clear, In my case I need to enumerate, and do a bunch of other iter manipulations

#

so I can't just take a slice either

pallid jungle
#

Hmmm....yeah, the problem with the code in that snippet is that oops is written to take a concrete type that's fixed in at the type level of Server - but since oops is called inside the spawn closure, with a shorter lived iterator the two lifetimes are incompatible 😦

Not sure I understand your comment about not being able to take a slice though - you could call iter and do your enumeration, iter manipulations, etc. inside the oops function if you took a slice

opaque crater
#

In my actual code, oops simply serializes the value and sends it. The Server type used in many contexts, and it wouldn't make sense to have a function for every way values might be transformed.

#

in other words, the iter manipulations are specific to this specific instance of Server, not all of them.

pallid jungle
#

hmm.....I still don't see where the generic "O" is needed tbh.....it sounds like oops only requires that it's an IterWrapper<T> where T: Iterator<&usize> so that could just be a bound on the oops function itself to be able to accept any iterator over borrows

opaque crater
#

The generic O is there for "business" reasons, in the sense that a given instance of Server should only send a given type of message

#

having the generic bound on oops would make it possible to send any kind of message

#

which is not a Rust error, but it's a business domain error

pallid jungle
#

Okay, hmm.....the crux of the issue is that the Server is of type Server<IterWrapper<Iter<'_, usize>> where the lifetime is inferred to be the lifetime of the Tokio runner

But the data that's actually passed to oops is an IterWrapper<Iter<'2, usize>> where that lifetime is a shorter lived lifetime from inside the closure

It seems like something that higher rank trait bounds might be able to help in some way though since what you want is to be able to fix the actual type of the message IterWrapper<Iter<???, usize>> but allow for ??? to vary

Seems like something that should be possible with some restructuring though, possibly using GATs perhaps

It would need some kind of generics lower than the Server level though because IterWrapper<Iter<'1, usize>> and IterWrapper<Iter<'2, usize>> are just not the same type so it makes sense it's complaining at the moment - we want to select the lifetime at the oops level but allow the overall structure of it to be fixed

opaque crater
#

Right

#

thanks for the help btw, it is much appreciated. You are very knowledgeable πŸ™‚

opaque crater
#

I think doing something like this would work?

#[derive(Default)]
struct Server;

trait SomeTrait {
    type O<'a>;
    fn oops<'a>(&self, _o: Self::O<'a>);
}

impl SomeTrait for Server {
    type O<'a> = IterWrapper<Iter<'a, usize>>;

    fn oops<'a>(&self, _o: Self::O<'a>) {}
}
#

not quite as ergonomic though

#

do you think this is fine? or do you think there's a better solution?

pallid jungle
#

That actually was going to be one of my suggestions tbh - to make the Server a trait with the message type being a GAT which allows the lifetime to be selected for the oops function. I would do it that way if it were me as it's gonna be nicer to work with IMO πŸ™‚

However, you can have a concrete Server type and allow for the oops method to be more lenient as long as the type O, the message type knows how to get a lifetime-remapped version of its type

It's not as nice looking but closer to your existing code with the Server<O> being a concrete type rather than a trait and just needing to implement the trait on the message type instead

#

?play

use std::{cell::RefCell, marker::PhantomData, slice::Iter};

#[derive(Default)]
struct Server<O> {
    phantom_o: PhantomData<fn() -> O>,
}

impl<'a, O: WithLifetime<'a>> Server<O> {
    fn oops(&'a self, _o: O::Type) {}
}

trait WithLifetime<'a> {
    type Type;
}

impl<'a, 'b, T: 'b> WithLifetime<'b> for IterWrapper<Iter<'a, T>> {
    type Type = IterWrapper<Iter<'b, T>>;
}

#[tokio::main]
async fn main() {
    let server = Server::<IterWrapper<Iter<usize>>> {
        phantom_o: Default::default(),
    };
    tokio::spawn(async move {
        let v = vec![];
        let x = IterWrapper::new(v.iter());
        server.oops(x);
    });
}

pub struct IterWrapper<I>(RefCell<I>);
impl<I> IterWrapper<I> {
    pub fn new(iterator: I) -> Self {
        Self(RefCell::new(iterator))
    }
}
astral roverBOT
pallid jungle
#

I would use your approach though and just make the entire Server a trait though tbh - but it's an option

opaque crater
#

ahhh yeah that's interesting!

#

Hmm I'll try to implement these ideas with my actual code and see how it flows. Not sure which one would work best yet.

#

thank you so much! I swear I've been stuck on this for literal days. First making a repro out of a large codebase, and then trying to make sense of it...

#

Your help was super valuable πŸ˜„ Thanks again.

pallid jungle
#

No problems, hope it works out for you πŸ™‚ - lifetime issues can be a pain in the butt sometimes haha