Asked  7 Months ago    Answers:  5   Viewed   134 times

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

trait Bar<T> {}

struct Foo<T> {
    data: Vec<Box<Bar<T>>>,
}

impl<T> Foo<T> {
    fn add<U: Bar<T>>(&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<T>). 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<T> is essentially std::unique_ptr<T>
  • 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

 Answers

44

Check out the entire error:

error[E0310]: the parameter type `U` may not live long enough
 --> src/main.rs:9:24
  |
8 |     fn add<U: Bar<T>>(&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<'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());
    }
}

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<T> {
    data: Vec<Box<dyn Bar<T>>>,
    // same as
    // data: Vec<Box<dyn Bar<T> + 'static>>,
} 

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

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

These two things need to match up.

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));
    }
}

or

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));
    }
}
Tuesday, June 1, 2021
 
StampyCode
answered 7 Months ago
38

There are actually plenty of types that can "not live long enough": all the ones that have a lifetime parameter.

If I were to introduce this type:

struct ShortLivedBee<'a>;
impl<'a> Animal for ShortLivedBee<'a> {}

ShortLivedBee is not valid for any lifetime, but only the ones that are valid for 'a as well.

So in your case with the bound

where T: Animal + 'static

the only ShortLivedBee I could feed into your function is ShortLivedBee<'static>.

What causes this is that when creating a Box<Animal>, you are creating a trait object, which need to have an associated lifetime. If you do not specify it, it defaults to 'static. So the type you defined is actually:

type RcAnimal = Rc<Box<Animal + 'static>>;

That's why your function require that a 'static bound is added to T: It is not possible to store a ShortLivedBee<'a> in a Box<Animal + 'static> unless 'a = 'static.


An other approach would be to add a lifetime annotation to your RcAnimal, like this:

type RcAnimal<'a> = Rc<Box<Animal + 'a>>;

And change your function to explicit the lifetime relations:

fn new_rc_animal<'a, T>(animal: T) -> RcAnimal<'a>
        where T: Animal + 'a { 
    Rc::new(Box::new(animal) as Box<Animal>)
}
Saturday, June 19, 2021
 
CMOS
answered 6 Months ago
18

Let’s compare the two definitions. First, the trait method:

fn to_c<'a>(&self, r: &'a Ref) -> Container<'a>;

And the implementation:

fn to_c(&self, r: &'a Ref) -> Container<'a>;

See the difference? The latter doesn’t have <'a>. <'a> has been specified elsewhere; the fact that it has the same name does not matter: it is a different thing entirely.

Functionally, your trait definition says that the returned container will have a reference inside it to something from r, but nothing from self. It may use self inside the method, but it may not store any references to it in the returned value.

Your method definition, however, is using a 'a that ties the lifetimes of r and the returned Container to self (that is, to the object itself, not the reference—the ?? in &'?? T<'??>—it’s a subtle but sometimes significant difference), whereas the trait definition had no such connection.

The two can be made to match by inserting the <'a> in the method definition in the implementation. But bear in mind that that is shadowing the 'a from ContainerB<'a>; it is not the same 'a! We’re better to give it another name; for convenience, I’ll make the change the other way round, changing it on the impl instead of the method (either would do):

impl<'b> ToC for ContainerB<'b> {
    fn to_c<'a>(&self, r: &'a Ref) -> Container<'a> {
        self.c
    }
}

But now of course you have a problem: the return value is of type Container<'b> (because that’s what the field c in a ContainerB<'b> is), but your signature demands Container<'a> (something using a reference from r, not from self).

One way which would fix it is specifying the lifetime of &self as 'a in both the trait definition and the implementation; in the implementation, this would then demand that 'b was greater than or equal to 'a (by virtue of the fact that you have successfully taken a reference with lifetime 'a to an object with lifetime 'b, and the object must outlive the reference) and so due to the subtyping ('a is a subtype of 'b) Container<'b> would be safely coerced to Container<'a>.

These sorts of lifetime matters are difficult to think about when you’re not familiar with them; but in time they become quite natural.

Wednesday, July 7, 2021
 
Hat
answered 5 Months ago
Hat
48

The std::thread::spawn() function is declared with a Send + 'static bound on its closure. Anything which this closure captures must satisfy the Send + 'static bound. There is no way around this in safe Rust. If you want to pass data to other threads using this function, it must be 'static, period.

It is possible to lift the 'static restriction with the proper API, see How can I pass a reference to a stack variable to a thread? for an example.

However, a 'static bound is not as scary as it may seem. First of all, you cannot force anything to do anything with lifetime bounds (you can't force anything to do anything with any kind of bounds). Bounds just limit the set of types which can be used for the bounded type parameter and that is all; if you tried to pass a value whose type does not satisfy these bounds, the compiler will fail to compile your program, it won't magically make the values "live longer".

Moreover, a 'static bound does not mean that the value must live for the duration of the program; it means that the value must not contain borrowed references with lifetimes other than 'static. In other words, it is a lower bound for possible references inside the value; if there are no references, then the bound does not matter. For example, String or Vec<u64> or i32 satisfy the 'static bound.

'static is a very natural restriction to put on spawn(). If it weren't present, the values transferred to another thread could contain references to the stack frame of the parent thread. If the parent thread finishes before the spawned thread, these references would become dangling.

Thursday, August 5, 2021
 
Rudie
answered 4 Months ago
78

When you have this kind of issue in a method, a good thing to do is to add an explicit lifetime to &self:

pub fn neighbors(&'a self) -> impl Iterator<Item = Point<'a>> {
    [(0, -1), (-1, 0), (1, 0), (1, 0)]
        .iter().map(|(dx, dy)| Point {
            board: self.board,
            x: self.x + dx,
            y: self.y + dy,
        })
}

The error is now better

error[E0373]: closure may outlive the current function, but it borrows `self`, which is owned by the current function
  --> src/main.rs:14:30
   |
14 |             .iter().map(|(dx, dy)| Point {
   |                         ^^^^^^^^^^ may outlive borrowed value `self`
15 |                 board: self.board,
   |                        ---- `self` is borrowed here
help: to force the closure to take ownership of `self` (and any other referenced variables), use the `move` keyword
   |
14 |             .iter().map(move |(dx, dy)| Point {
   |                         ^^^^^^^^^^^^^^^

You then just need to add the move keyword as advised by the compiler, to say to it that you will not use &'a self again.

Note that the lifetime of self has not to be the same as the lifetime of Point. This is better to use this signature:

fn neighbors<'b>(&'b self) -> impl 'b + Iterator<Item = Point<'a>>
Friday, August 6, 2021
 
Shreejibawa
answered 4 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share