简体   繁体   中英

How to implement a trait with 'static lifetime for a struct with lifetime 'a?

I have a trait Surface: 'static that I want to implement for a struct Obj<'a> . The trait needs to be 'static because I want to store objects from type Surface in a Vec<Box<Surface>> .

In the first step I tried this.

impl<'a> Surface for Obj<'a> {}

This will not work because of a lifetime mismatch between 'static and 'a . In other words: Surface can live longer than Obj because Surface is 'static . I changed my implementation as follows.

impl<'a> Surface for Obj<'a> where 'a: 'static {}

As far as I understand the documentation correctly, what I'm doing is, 'a can outlive 'static . Do I want this?

If I transfer the ownership of Obj<'a> , the compiler tells me that a mutable reference inside Obj will not live long enough and is still borrowed.

Here is a short example.

trait Surface: 'static {}

struct Manager {
    storage: Vec<Box<Surface>>,
}

impl Manager {
    fn add(&mut self, surface: impl Surface) {
        self.storage.push(Box::new(surface));
    }
}

struct SomeOtherStruct {}

struct Obj<'a> {
    data: &'a mut SomeOtherStruct,
}

impl<'a> Obj<'a> {
    fn new(some_struct: &'a mut SomeOtherStruct) -> Self {
        Obj { data: some_struct }
    }
}

impl<'a> Surface for Obj<'a> where 'a: 'static {}

fn main() {
    let mut some_struct = SomeOtherStruct {};
    let mut manager = Manager {
        storage: Vec::new(),
    };

    let obj = Obj::new(&mut some_struct);
    manager.add(obj);
}

( Playground )

error[E0597]: `some_struct` does not live long enough
  --> src/main.rs:33:24
   |
33 |     let obj = Obj::new(&mut some_struct);
   |               ---------^^^^^^^^^^^^^^^^-
   |               |        |
   |               |        borrowed value does not live long enough
   |               argument requires that `some_struct` is borrowed for `'static`
34 |     manager.add(obj);
35 | }
   | - `some_struct` dropped here while still borrowed

In other words &mut some_struct is lifetime 'a but needs 'static . Ok it's clear because some_struct lives in Obj<'a> so it cannot be 'static ?

Is this what I'm trying to do "Rust like"? I've no idea how to get it to work. Its really confusing with the lifetimes. I think I can get around this by using a Rc<T> , but this will make things more complex.

First things first:

impl<'a> Surface for Obj<'a> where 'a: 'static {}

is verbose for

impl Surface for Obj<'static> {}

You identified your problem correctly:

In other words &mut some_struct is lifetime 'a but needs 'static

You need to declare some_struct as static :

fn main() {
    static mut SOME_STRUCT: SomeOtherStruct = SomeOtherStruct {};
    // ...
    let obj = unsafe { Obj::new(&mut SOME_STRUCT) };
    //  ...
}

The problem is, that you cannot access mutable static variables safely, because they can be mutated be multiple thread simultaneously and that is a problem, hence you need unsafe .

So no, your code is not "Rust like", but I'm afraid you can't change that with your current architecture.


The trait needs to be 'static because I want to store objects from type Surface in a Vec<Box<Surface>> .

I don't get why you think you need 'static in the first place, eg this code is perfectly legal:

trait Foo {}
struct Bar;

impl Foo for Bar {}

fn main() {
    let b: Box<Foo> = Box::new(Bar);
}

How to implement a trait with 'static lifetime for a struct with lifetime 'a ?

You do not and cannot. The purpose of the 'static lifetime is to say "something that lives for the entire duration of the program". No arbitrary lifetime 'a meets this requirement except 'static itself.

@hellow's answer works and solves my problem, but it feels hacky and working against Rust.

With your hints, I found a better solution that alsos work and does not use unsafe .

Solution 1

I specified explicit lifetime parameters for the Manager and for the type Box<Surface + 'a> :

trait Surface {}

struct Manager<'a> {
    storage: Vec<Box<Surface + 'a>>,
}

impl<'a> Manager<'a> {
    fn add(&mut self, surface: impl Surface + 'a) {
        self.storage.push(Box::new(surface));
    }
}

struct SomeOtherStruct {}

struct Obj<'a> {
    data: &'a mut SomeOtherStruct,
}

impl<'a> Obj<'a> {
    fn new(some_struct: &'a mut SomeOtherStruct) -> Self {
        Obj {
            data: some_struct
        }
    }
}

impl<'a> Surface for Obj<'a> {}

fn main() {
    let mut some_struct = SomeOtherStruct{};
    let mut manager = Manager { storage: Vec::new() };

    let obj = Obj::new(&mut some_struct);
    manager.add(obj);
}

( Playground )

Solution 2

Store a Box<SomeOtherStruct> instead of &mut SomeOtherStruct in Obj . This will eliminate the lifetimes:

trait Surface {}

struct Manager {
    storage: Vec<Box<Surface>>,
}

impl Manager {
    fn add(&mut self, surface: impl Surface + 'static) {
        self.storage.push(Box::new(surface));
    }
}

struct SomeOtherStruct {}

struct Obj {
    data: Box<SomeOtherStruct>,
}

impl Obj {
    fn new(some_struct: Box<SomeOtherStruct>) -> Self {
        Obj {
            data: some_struct
        }
    }
}

impl Surface for Obj {}

fn main() {
    let some_struct = SomeOtherStruct{};
    let mut manager = Manager { storage: Vec::new() };

    let obj = Obj::new(Box::new(some_struct));
    manager.add(obj);
}

( Playground )

In my opinion both solutions are good. I don't know which solution is better and I've no experience with pro and cons of this solutions. For me (maybe because I'm a beginner and still leaning Rust) it's easier to avoid lifetimes and use Box , Rc etc.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM