2

I would like to write a function that parses a string and returns one of several structs that implement a trait object that allows handing out references to the implementing struct. The use case is reading a string from a configuration file that determines which implementation is used. The code looks like this:

trait Foo<'a> {
    fn get(&'a self) -> &'a i32;
}

struct Bar {
    data: i32,
}

impl<'a> Foo<'a> for Bar {
    fn get(&'a self) -> &'a i32 {
        &self.data
    }
}

struct Baz {
    data: i32,
}

impl<'a> Foo<'a> for Baz {
    fn get(&'a self) -> &'a i32 {
        &self.data
    }
}

fn get_foo(foo: &str) -> Box<dyn Foo> {
    let split = foo.splitn(2, "+").collect::<Vec<_>>();
    let data = i32::from_str_radix(split[1], 10).unwrap();

    if foo.starts_with("bar") {
        Box::new(Bar { data })
    } else {
        Box::new(Baz { data })
    }
}

fn main() {
    let foo = get_foo("bar+0");
    println!("Foo: {}", foo.get());
}

However, when I do that, I get this error:

error[E0597]: `*foo` does not live long enough
  --> src/main.rs:38:25
   |
38 |     println!("Foo: {}", foo.get());
   |                         ^^^ borrowed value does not live long enough
39 | }
   | - `*foo` dropped here while still borrowed
   |
   = note: values in a scope are dropped in the opposite order they are created

Is this possible to do in Rust?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
user1769868
  • 133
  • 7
  • 2
    *Why* did you put that lifetime on your trait as opposed to on the method (or not at all, allowing elision to do its job)? – Shepmaster Oct 16 '18 at 15:25
  • Have you tried removing lifetime specifiers from the return values of the `get` methods? – PitaJ Oct 16 '18 at 15:27
  • 2
    I believe your question is answered by the answers of [What are the differences between specifying lifetime parameters on an impl or on a method?](https://stackoverflow.com/q/31470146/155423) and [What does it mean for a trait to have a lifetime parameter?](https://stackoverflow.com/q/29975854/155423). If you disagree, please [edit] your question to explain the differences. Otherwise, we can mark this question as already answered. – Shepmaster Oct 16 '18 at 15:27
  • See also [type parameter for function vs struct (lifetime issue)](https://stackoverflow.com/q/28131319/155423); [How to solve this “does not live long enough”?](https://stackoverflow.com/q/44059574/155423); – Shepmaster Oct 16 '18 at 15:31
  • @Shepmaster: Thanks, moving the lifetime from the trait to individual methods worked. If I'm understanding your `Keeper` link correctly, the use case for a lifetime on a trait object is when you'd like a struct with an existing lifetime to impl it? e.g. in the example above, it looks like I would need a lifetime on the trait if I was using `struct Bar<'a>`. – user1769868 Oct 17 '18 at 14:50
  • 1
    @user1769868 that is my understanding, yes. – Shepmaster Oct 17 '18 at 15:00

1 Answers1

0

Thanks to @Shepmaster for pointing out that a lifetime on the trait object is not the way to go. Moving the lifetime to individual methods (which in turn can be elided in this example) works great:

trait Foo {
    fn get(&self) -> &i32;
}

struct Bar {
    data: i32,
}

impl Foo for Bar {
    fn get(&self) -> &i32 {
        &self.data
    }
}

struct Baz {
    data: i32,
}

impl Foo for Baz {
    fn get(&self) -> &i32 {
        &self.data
    }
}

fn get_foo(foo: &str) -> Box<dyn Foo> {
    let split = foo.splitn(2, "+").collect::<Vec<_>>();
    let data = i32::from_str_radix(split[1], 10).unwrap();

    if foo.starts_with("bar") {
        Box::new(Bar { data })
    } else {
        Box::new(Baz { data })
    }
}

fn main() {
    let foo = get_foo("bar+0");
    println!("Foo: {}", foo.get());
}
user1769868
  • 133
  • 7