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:
#![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?
Ok, so this is where HRTB would really help . 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.
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.
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.