Specify lifetime of return value in trait

Hey,

I have a trait that defines a function that takes a reference to something and stores it in a struct like so:

trait Lifetimeless {
    fn create<'r>(data: &'r [i32]) -> Self;
}

The struct itself has a lifetime parameter and is defined like so:

struct Container<'c> {
    content: &'c [i32],
}

When implementing the trait for the struct, I get the following:

impl<'c> Lifetimeless for Container<'c> {
    fn create<'r>(data: &'r [i32]) -> Self {
        Self {content: data}
    }
}

This won’t compile, as it leaks the reference with lifetime 'r as a reference with lifetime 'c, which would require 'r: 'c.
So I want to restrict that somehow. But I can’t simply do it, since I don’t have access to the lifetime 'c in the trait.
For complicated reasons I don’t yet fully understand, I cannot simply give Lifetimeless as lifetime parameter and use it to define the create function. It leads to lifetime errors in a different part of my program where I use Lifetimeless as a type bound.

So, what I would like to do is define the Lifetimeless trait like this:

trait Lifetimeless {
    fn create<'r>(data: &'r [i32]) -> Self
    where 'r: Self;
}

Meaning, that Self gets destroyed before 'r ends.
Is there any way to achieve that in Rust in its current state?

1 Like

Is there any problem with following?

trait Lifetimeless<'r> {
    fn create(data: &'r [i32]) -> Self;
}

struct Container<'c> {
    content: &'c [i32],
}

impl<'c> Lifetimeless<'c> for Container<'c> {
    fn create(data: &'c [i32]) -> Self {
        Self {content: data}
    }
}

I think this is what @isibboi was referring to in:

But we should talk about this bit because giving Lifetimeless a generic lifetime parameter is the way to go.

Here is a playground with the problem I get from adding a lifetime parameter to the Lifetimeless trait
And here is the code from the playground:

#![feature(nll)]

trait NotLifetimeless<'n> {
    fn create(&'n [i32]) -> Self;

    fn eval(&self) -> i32;
}

#[derive(Debug)]
struct WithLifetime<'a> {
    data: &'a [i32],
}

impl<'a> NotLifetimeless<'a> for WithLifetime<'a> {
    fn create(data: &'a [i32]) -> Self {
        Self { data }
    }

    fn eval(&self) -> i32 {
        self.data[0]
    }
}

#[derive(Debug)]
struct Operation {
    data: Vec<i32>,
}

impl Operation {
    fn do_things<'d, T: NotLifetimeless<'d>>(&'d mut self) {
        let mut v = Vec::new();

        for i in 0..self.data.len() {
            let t = T::create(&self.data[i..=i]);
            v.push(t.eval());
        }

        self.data = v;
    }
}

fn main() {
    let mut op = Operation {
        data: vec![4, 5, 6],
    };
    op.do_things::<WithLifetime>();
    println!("Result: {:?}", op);
}

The compiler gives the following error message:

error[E0506]: cannot assign to `self.data` because it is borrowed
  --> src/main.rs:38:9
   |
30 |     fn do_things<'d, T: NotLifetimeless<'d>>(&'d mut self) {
   |                  -- lifetime `'d` defined here
...
34 |             let t = T::create(&self.data[i..=i]);
   |                     ----------------------------
   |                     |          |
   |                     |          borrow of `self.data` occurs here
   |                     argument requires that `self.data` is borrowed for `'d`
...
38 |         self.data = v;
   |         ^^^^^^^^^ assignment to borrowed `self.data` occurs here

Something happens to the lifetime of self when calling create that I don’t understand.
How can I change this to make it build?

What’s needed here is an HRTB bound:

fn do_things<T: for<'d> NotLifetimeless<'d>>(&mut self)

The problem is compiler thinks WithLifetime doesn’t satisfy the bound, which I believe is a known bug/limitation in how HRTB is handled.

One workaround would be to return the Vec<i32> from do_things and then set the data field afterwards: example

Thank you, this is very helpful already.
Unfortunately when applying the fix to my code, another problem arises.
I added code to your fix that shows the problem:

#![feature(nll)]

trait NotLifetimeless<'n> {
    fn create(&'n [i32]) -> Self;

    fn eval(&self) -> i32;
}

#[derive(Debug)]
struct WithLifetime<'a> {
    data: &'a [i32],
}

impl<'a> NotLifetimeless<'a> for WithLifetime<'a> {
    fn create(data: &'a [i32]) -> Self {
        Self { data }
    }

    fn eval(&self) -> i32 {
        self.data[0]
    }
}

#[derive(Debug)]
struct Operation {
    data: Vec<i32>,
}

impl Operation {
    fn do_things<'d, T: NotLifetimeless<'d>>(&'d self) -> Vec<i32> {
        let mut u = Vec::new();
        let mut v = Vec::new();

        for i in 0..self.data.len() {
            let s = T::create(&self.data[i..=i]);
            u.push(s.eval());
            let t = T::create(&u);
            v.push(t.eval());
        }
        v
    }
}

fn main() {
    let mut op = Operation {
        data: vec![4, 5, 6],
    };
    let v = op.do_things::<WithLifetime>();
    op.data = v;
    println!("Result: {:?}", op);
}

I see a problem with this.

let t = T::create(&u);

This tries to make a reference with the lifetime of T, which comes from main, using as input the local variable u. We can check that

let t = WithLifetime::create(&u);

runs fine. However I guess that is not what you want.

Perhaps the create method should try to restrict the lifetime. I tried the following, but I cannot get it to compile.

fn create<'x:'a>(&'x [i32]) -> impl NotLifetimeless<'x>;

Ok, so this is where HRTB would really help :slight_smile:. I think you should file a github issue with this example; as mentioned, I believe this is a known problem although I don’t know which existing github issue would be best.

I have thought more this problem and I think I have a solution using HRTBs consisting on using separate traits for the creation and evaluation. Here is the code.

trait Evaluable{
    fn eval(&self) -> i32;
}

trait Factory<'a>{
    type T: Evaluable;
    fn create(&'a [i32]) ->  Self::T;
}

#[derive(Debug)]
struct WithLifetime<'a> {
    data: &'a [i32],
}

struct WFactory;

impl<'a> Factory<'a> for WFactory
{
    type T=WithLifetime<'a>;
    fn create(data: &'a [i32]) -> WithLifetime<'a> {
        WithLifetime { data }
    }

}

impl<'a> Evaluable for WithLifetime<'a> {
    fn eval(&self) -> i32 {
        self.data[0]
    }
}

#[derive(Debug)]
struct Operation {
    data: Vec<i32>,
}

impl Operation {
    fn do_things<F: for<'x> Factory<'x> >(&self) -> Vec<i32> {
        let mut u = Vec::new();
        let mut v = Vec::new();

        for i in 0..self.data.len() {
            let s = F::create(&self.data[i..i+1]);
            u.push(s.eval());
            let t = F::create(&u);
            v.push(t.eval());
        }
        v
    }
}

fn main() {
    let mut op = Operation {
        data: vec![4, 5, 6],
    };
    let v = op.do_things::<WFactory>();
    op.data = v;
    println!("Result: {:?}", op);
}

In brief, we implement in WFactory a Factory<'x> for every lifetime 'x, which enables us to do_things using a HRTB. Thus, the factory can be used to create evaluables based on different lifetimes.

How is this a bug? When T::create is called in the function body, the argument &self.data[i..=i] carries an additional read-lock on self.data that is not represented in 'd.

In this manner, anything used with references created inside a function's body requires HRTB.

I’m not sure what you’re getting at, to be honest. This should work, but doesn’t.

Okay, seems the problem there is that there is no valid substitution for the lifetime parameter in the turbofish on do_things. So I guess what you meant when you said “HRTB would really help” is to have more “HRTB-like types” similar to for<'a> fn(&'a i32)?

fn main() {
    let mut op = Operation {
        data: vec![4, 5, 6],
    };
    let v = op.do_things::<for<'a> WithLifetime<'a>>();
    op.data = v;
    println!("Result: {:?}", op);
}

…well… no, hang on, for<'a> WithLifetime<'a>> wouldn’t make sense because WithLifetime is covariant. It’d be like for<'a> &'a T (in other words… what?).

So I’m still not sure what feature request or bugfix you are suggesting.

I’m saying that the compiler is not inferring an HRTB region (not sure that’s the correct technical term) for WithLifetime, but rather a concrete lifetime/region it’s selecting for the call. In other words, we shouldn’t need a workaround like @nakacristo suggested as it doesn’t express anything above and beyond what’s already present in the code I put.

There are some differences. From this

impl<'a> NotLifetimeless<'a> for WithLifetime<'a>

to this

impl<'a> Factory<'a> for WFactory

That is, my WFactory implements Factory<'a> for any lifetime 'a. In contrast for any given lifetime 'a, WithLifetime<'a> implements just NotLifetimeless<'a>.

If there is a bug I think it is only on the error messages. Perhaps a new feature could help, but I am unsure about which should be.

I’m not sure that distinction matters in the context of code discussed here, although that may be close to the “technical” reason it doesn’t work today. The WithLifetime<'a>: NotLifetimeless<'a> should be “lifted” to match T: for<'a> NotLifetimeless<'a>. I don’t see any immediate soundness issue, and the WFactory just seems like extra boilerplate that doesn’t actually express anything more, from a user’s perspective.

Sorry to resurrect, but I found that I couldn't put a filetime on Self, but I could write:

impl<'c> Lifetimeless for Container<'c> {
    fn create<'r>(data: &'r [i32]) -> Container<'r> {
        Container {content: data}
    }
}

which really felt wrong. I don't know if this solve the problem for you.