1

The Path argument could be immediately converted into a PathBuf, but that seems inefficient. There has to be some way of keeping just a Path, right?

use std::{fs::File, path::Path};

struct Foo {
    a: Option<File>,
    b: Option<File>,
}

struct FooBuilder<'a> {
    a: Option<&'a Path>,
    b: Option<&'a Path>,
}

impl<'a> FooBuilder<'a> {
    fn new() -> FooBuilder<'a> {
        FooBuilder { a: None, b: None }
    }

    fn a<P: AsRef<Path> + 'a>(&'a mut self, a: P) -> &mut FooBuilder<'a> {
        self.a = Some(a.as_ref());
        self
    }

    fn b<P: AsRef<Path> + 'a>(&'a mut self, b: P) -> &mut FooBuilder<'a> {
        self.b = Some(b.as_ref());
        self
    }

    fn done(&self) -> Foo {
        Foo {
            a: match self.a {
                Some(path) => Some(File::open(path).unwrap()),
                None => None,
            },
            b: match self.b {
                Some(path) => Some(File::open(path).unwrap()),
                None => None,
            },
        }
    }
}

fn main() {
    let path1 = Path::new("1");
    let path2 = Path::new("2");
    let _foo = FooBuilder::new().a(path1).b(path2).done();
}
error[E0597]: `a` does not live long enough
  --> src/main.rs:19:23
   |
13 | impl<'a> FooBuilder<'a> {
   |      -- lifetime `'a` defined here
...
19 |         self.a = Some(a.as_ref());
   |         --------------^----------
   |         |             |
   |         |             borrowed value does not live long enough
   |         assignment requires that `a` is borrowed for `'a`
20 |         self
21 |     }
   |     - `a` dropped here while still borrowed

error[E0597]: `b` does not live long enough
  --> src/main.rs:24:23
   |
13 | impl<'a> FooBuilder<'a> {
   |      -- lifetime `'a` defined here
...
24 |         self.b = Some(b.as_ref());
   |         --------------^----------
   |         |             |
   |         |             borrowed value does not live long enough
   |         assignment requires that `b` is borrowed for `'a`
25 |         self
26 |     }
   |     - `b` dropped here while still borrowed
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
waxogi
  • 13
  • 4

1 Answers1

2

This works:

use std::{fs::File, path::Path};

struct Foo {
    a: Option<File>,
}

struct FooBuilder<'a> {
    a: Option<&'a Path>,
}

impl<'a> FooBuilder<'a> {
    fn new() -> FooBuilder<'a> {
        FooBuilder { a: None }
    }

    fn a<P>(&mut self, a: &'a P) -> &mut FooBuilder<'a>
    where
        P: AsRef<Path> + ?Sized,
    {
        self.a = Some(a.as_ref());
        self
    }

    fn build(&self) -> Foo {
        Foo {
            a: self.a.map(|path| File::open(path).unwrap()),
        }
    }
}

fn main() {
    let path1 = Path::new("1");
    let _foo = FooBuilder::new().a(path1).build();
}

Let's focus on the a method:

fn a<P>(&mut self, a: &'a P) -> &mut FooBuilder<'a>
where
    P: AsRef<Path> + ?Sized,

This method accepts a reference to a type that implements AsRef<Path>. That means that we can get a reference to a Path with the same lifetime as the parameter. The other change is to make the Sized bound optional for the type via ?. This means that we can have a reference to something that we don't know how big it is. This is fine as we will know how big the reference itself is.

Let's compare this to your original version:

fn a<P: AsRef<Path> + 'a>(&'a mut self, a: P) -> &mut FooBuilder<'a> {
    self.a = Some(a.as_ref());
    self
}

Here, the a parameter is passed-by-value into the method a. When you call as_ref, you are implicitly calling it on a reference to the item that is on the stack frame of the method call. The referred-to item will be dropped at the end of the method call, which means that the reference would become invalid. That is the reasoning behind the error: `a` does not live long enough error you were getting.

I also used Option::map to clean up the build method. I renamed it to build because builders should generally have a build method, unless there's a more obvious verb to use (like open).

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • this helped me. i'd like to add something that is stated several times in the rust book, as well as implicitly here, but still may slip up folks new to rust: `Sized` is the default and implicit. – user4893106 Dec 02 '20 at 02:36