Lifetime of Struct used in GenericType


#1

Hi,

I am writing a program that allow users to define arbitrary mathematical functions to compute some values. But I have an issue of specifying lifetime for struct that is used as generic type. Here is a minimal example that demonstrate it, so overall, I have models, which have list of functions, and a function can compute value from variables

pub trait Function<V> {
    fn exec(&self, var: &V);
}

pub struct Variable<'a> {
    name: &'a str,
}

pub struct FunctionImpl {
}

impl<'a> Function<Variable<'a>> for FunctionImpl {
    fn exec(&self, var: &Variable<'a>) {
        println!("exec func");
    }
}

pub struct Model<V> {
    funcs: Vec<Box<Function<V>>>,
}

impl<V> Model<V> {
    pub fn new(funcs: Vec<Box<Function<V>>>) -> Model<V> {
        Model { funcs }
    }

    pub fn exec(&self, var: &V) {
        println!("exec model");
    }
}

The problem here is sometime structs implement V (like Variable struct) can contain references. Which make this code unable to compile.

fn main() {
    let model = Model::new(vec![Box::new(FunctionImpl {})]);
    {
        let example = Variable {
            name: "someone",
        };
        model.exec(&example);
    }

    {
        let x = "someoneelse".to_owned();
        let example = Variable {
            name: &x,
        };
        model.exec(&example);
    }
}

Error:


59 |             name: &x,
   |                    ^ borrowed value does not live long enough
...
62 |     }
   |     - `x` dropped here while still borrowed
...
65 | }
   | - borrowed value needs to live until here

Which I think life time 'a of trait Function bounded outside, so it is outlive lifetime of x. However, since I cannot store any value of V inside Function (func exec is immutable), and model; I was hope that Rust can detect that lifetime 'a only bound to the function exec, and can cast life time 'a to the shorter lifetime of x.

Also, I found this code can compile well.

fn main() {
  let func = FunctionImpl {};
  {
        let example = Variable {
            name: "someone",
        };
        func.exec(&example);
    }

    {
        let x = "someoneelse".to_owned();
        let example = Variable {
            name: &x,
        };
        func.exec(&example);
    }
}

I got one solution that use Rc/Arc for reference, so I don’t need lifetime. However, I want to avoid it because I may have to deal with creating millions of variables, so Rc/Arc can be costly.

My question is: how can I overcome this problem? Is Rust actually correct to prevent possible error with this design?

Thank you for your time! Any help is greatly appreciate!


#2

Are you certain that you have to use temporary str reference instead of an owned String?

I see you use it with a string literal. If you only ever want literals, then remove the lifetime from the struct and use &'static str. Otherwise don’t put references in structs. Use owned types there — String in this case.


#3

Thanks for your reply. &str is for demonstration purpose, it keeps the example as minimal as possible. In my app, it is a reference to a graph of thousand nodes.


#4

I think this has to do with trait objects being invariant over their type parameters (or associated types).

In short, the compiler is not able to infer the variance of V in Function<V> when it’s a trait object. As such, it can’t rule out that the V argument may get stored internally, and if V doesn’t outlive the Model (like your second example), it would lead to unsoundness; as such it wants all &str to outlive model.

What you’d likely want to say is Box<for<'a> Function<V<'a>>>> but that requires V to be a concrete type (in today’s Rust, at least).

I’d be happy if someone can correct my understanding or elaborate on it.


Trouble understanding conflicting lifetime requirements with generic types