HiveBrain v1.2.0
Get Started
← Back to all entries
patternrustMajor

The compiler suggests I add a 'static lifetime because the parameter type may not live long enough, but I don't think that's what I want

Submitted by: @import:stackoverflow-api··
0
Viewed 0 times
thinklongcompilernotsuggestsmayaddbutwantwhat

Problem

I'm trying to implement something that looks like this minimal example:

trait Bar {}

struct Foo {
    data: Vec>>,
}

impl Foo {
    fn add>(&mut self, x: U) {
        self.data.push(Box::new(x));
    }
}


Since Rust defaults to (as far as I can tell) pass-by-ownership, my mental model thinks this should work. The add method takes ownership of object x and is able to move this object into a Box because it knows the full type U (and not just trait Bar). Once moved into a Box, the lifetime of the item inside the box should be tied to the actual lifetime of the box (e.g., when pop()ed off the vector the object will be destroyed).

Clearly, however, the compiler disagrees (and I'm sure knows a bit more than I...), asking me to consider adding a 'static lifetime qualifier (E0310). I am 99% sure that's not what I want, but I'm not exactly sure what I'm supposed to do.

To clarify what I'm thinking and help identify misconceptions, my mental model, coming from a C++ background, is:

  • Box is essentially std::unique_ptr



  • Without any annotations, variables are passed by value if Copy and rvalue-reference otherwise



  • With a reference annotation, & is roughly const& and &mut is roughly &



  • The default lifetime is lexical scope

Solution

Check out the entire error:

error[E0310]: the parameter type U may not live long enough
--> src/main.rs:9:24
|
8 | fn add>(&mut self, x: U) {
| -- help: consider adding an explicit lifetime bound
U: 'static...
9 | self.data.push(Box::new(x));
| ^^^^^^^^^^^
|
note: ...so that the type
U will meet its required lifetime bounds
--> src/main.rs:9:24
|
9 | self.data.push(Box::new(x));
| ^^^^^^^^^^^


Specifically, the compiler is letting you know that it's possible that some arbitrary type U might contain a reference, and that reference could then become invalid:

impl Bar for &'a str {}

fn main() {
    let mut foo = Foo { data: vec![] };

    {
        let s = "oh no".to_string();
        foo.add(s.as_ref());
    }
}


That would be Bad News.

Whether you want a 'static lifetime or a parameterized lifetime is up to your needs. The 'static lifetime is easier to use, but has more restrictions. Because of this, it's the default when you declare a trait object in a struct or a type alias:

struct Foo {
    data: Vec>>,
    // same as
    // data: Vec + 'static>>,
}


However, when used as an argument, a trait object uses lifetime elision and gets a unique lifetime:

fn foo(&self, x: Box>)
// same as
// fn foo(&'a self, x: Box + 'b>)


These two things need to match up.

struct Foo {
    data: Vec + 'a>>,
}

impl Foo {
    fn add(&mut self, x: U)
    where
        U: Bar + 'a,
    {
        self.data.push(Box::new(x));
    }
}


or

struct Foo {
    data: Vec>>,
}

impl Foo {
    fn add(&mut self, x: U)
    where
        U: Bar + 'static,
    {
        self.data.push(Box::new(x));
    }
}

Code Snippets

impl<'a, T> Bar<T> for &'a str {}

fn main() {
    let mut foo = Foo { data: vec![] };

    {
        let s = "oh no".to_string();
        foo.add(s.as_ref());
    }
}
struct Foo<T> {
    data: Vec<Box<dyn Bar<T>>>,
    // same as
    // data: Vec<Box<dyn Bar<T> + 'static>>,
}
fn foo(&self, x: Box<dyn Bar<T>>)
// same as
// fn foo<'a, 'b>(&'a self, x: Box<dyn Bar<T> + 'b>)
struct Foo<'a, T> {
    data: Vec<Box<dyn Bar<T> + 'a>>,
}

impl<'a, T> Foo<'a, T> {
    fn add<U>(&mut self, x: U)
    where
        U: Bar<T> + 'a,
    {
        self.data.push(Box::new(x));
    }
}
struct Foo<T> {
    data: Vec<Box<dyn Bar<T>>>,
}

impl<T> Foo<T> {
    fn add<U>(&mut self, x: U)
    where
        U: Bar<T> + 'static,
    {
        self.data.push(Box::new(x));
    }
}

Context

Stack Overflow Q#40053550, score: 95

Revisions (0)

No revisions yet.