0

I would like to hide the actual implementation that is returned from the create function by returning an impl trait as in create_trait(). How could this be done?

trait Names<'a> {
    fn names(&'a self) -> &Vec<&'a str>;
}

struct NamesImpl<'b> {
    names: Vec<&'b str>,
}

impl<'c> Names<'c> for NamesImpl<'c> {
    fn names(&'c self) -> &Vec<&'c str> {
        &self.names
    }
}

fn create_impl<'i>() -> NamesImpl<'i> {
    NamesImpl {
        names: vec!["Hello", "world"],
    }
}

#[allow(dead_code)]
fn create_trait<'t>() -> impl Names<'t> {
    NamesImpl {
        names: vec!["Hello", "world"],
    }
}

fn main() {
    let names_impl = create_impl();
    println!("Names: {:?}", names_impl.names());

    //This doesn't compile, see error below
    let names_trait = create_trait();
    println!("Names: {:?}", names_trait.names());
}

I can't wrap my head around the following compile error. Why does it work with create_impl() but not with create_trait()?

error[E0597]: `names_trait` does not live long enough
  --> src/main.rs:34:29
   |
34 |     println!("Names: {:?}", names_trait.names());
   |                             ^^^^^^^^^^^ borrowed value does not live long enough
35 | }
   | -
   | |
   | `names_trait` dropped here while still borrowed
   | borrow might be used here, when `names_trait` is dropped and runs the destructor for type `impl Names<'_>`
pretzelhammer
  • 13,874
  • 15
  • 47
  • 98
Paul
  • 11
  • 2
  • 1
    `fn create_trait<'t>() -> impl Names<'t>` is generally nonsense. See [Is there any way to return a reference to a variable created in a function?](https://stackoverflow.com/q/32682876/155423). The only possible lifetime for `'t` here is `'static`, but then your function has to take a `'static` reference and return `'static` references. – Shepmaster Dec 02 '20 at 21:14
  • Hey Paul I'd strongly recommend you to read [Common Rust Lifetime Misconceptions](https://github.com/pretzelhammer/rust-blog/blob/master/posts/common-rust-lifetime-misconceptions.md). I think you'll find it very helpful if you're new to Rust. It shows a lot of examples including when it's appropriate to use explicit lifetimes vs compiler-inferred lifetimes. – pretzelhammer Dec 02 '20 at 21:41
  • Thanks for the answers. I'll have some more reading to do, I guess. Some things are still unclear to me. – Paul Dec 02 '20 at 23:13

2 Answers2

4

A general rule of thumb when it comes to using lifetimes in Rust is that if you don't know what you're doing, let the compiler infer the correct lifetimes for you. Once you get more experience, you'll learn when you actually need to use explicit lifetime annotations, but your example is not one of those situations. I was able make it compile by removing all the unnecessary lifetime annotations:

trait Names {
    fn names(&self) -> &Vec<&str>;
}

struct NamesImpl<'a> {
    names: Vec<&'a str>,
}

impl Names for NamesImpl<'_> {
    fn names(&self) -> &Vec<&str> {
        &self.names
    }
}

fn create_impl() -> NamesImpl<'static> {
    NamesImpl { names: vec!["Hello", "world"] }
}

fn create_trait() -> impl Names {
    NamesImpl { names: vec!["Hello", "world"] }
}

fn main() {
    let names_impl = create_impl();
    println!("Names: {:?}", names_impl.names());

    // compiles
    let names_trait = create_trait();
    println!("Names: {:?}", names_trait.names());
}

playground

To determine which lifetime annotations were unnecessary, I started by removing all of them and then only added lifetime annotations back to the areas where the compiler specifically asked me to.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
pretzelhammer
  • 13,874
  • 15
  • 47
  • 98
  • 2
    _"I started by removing all of them and then only added lifetime annotations back to the areas where the compiler specifically asked me to"_ The last paragraph is best advice I ever read about lifetime. – Milan Jaric Dec 03 '20 at 00:37
1

This section in Common Rust Lifetime Misconceptions (Thank you pretzelhammer!) was an eye opener to me. It made mee see that in this construction

trait Names<'a> {
    fn names(&'a self) -> &Vec<&'a str>;
}

calling names() will borrow the struct for the rest of its lifetime.

Correcting this in the original code with explicit lifetime annotations is possible, but the result is far from beautiful. I just tried this in an attempt to better understand the 'why'.

trait Names<'a : 'f, 'f> {
    fn names(&self) -> &Vec<&'f str>;
}

struct NamesImpl<'b> {
    names: Vec<&'b str>,
}

impl <'c : 'f, 'f> Names<'c, 'f> for NamesImpl<'c> {
    fn names(&self) -> &Vec<&'f str> {
        &self.names
    }
}

fn create_impl<'i : 'f, 'f>() -> NamesImpl<'i> {
    NamesImpl{names: vec!["Hello", "world"]}
}

#[allow(dead_code)]
fn create_trait<'t : 'f, 'f>() -> impl Names<'t, 'f> {
    NamesImpl{names: vec!["Hello", "world"]}
}

fn main() {
    let names_impl = create_impl();
    println!("Names: {:?}", names_impl.names());

    //This compiles
    let names_trait = create_trait();
    println!("Names: {:?}", names_trait.names());
}

Conclusion: Follow the advice given by Shepmaster

Paul
  • 11
  • 2