如何编写存储路径的构建器?

How can I write a builder that stores a Path?

Path 参数可以立即转换为 PathBuf,但这似乎效率低下。必须有一些方法只保留一个 Path,对吗?

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

这个有效:

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

让我们关注a方法:

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

此方法接受对实现 AsRef<Path> 的类型的 引用 。这意味着我们可以获得与参数具有相同生命周期的 Path 的引用。另一个变化是通过 ? 使 Sized 绑定对于类型是可选的。这意味着我们可以参考一些我们不知道它有多大的东西。这很好,因为我们会知道 引用本身 有多大。

让我们将其与您的原始版本进行比较:

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

此处,a 参数按值传递到方法 a 中。当您调用 as_ref 时,您是在 reference 上隐式调用它,指向方法调用的堆栈帧上的项目。引用的项目将在方法调用结束时被删除,这意味着引用将变得无效。这就是您遇到 error: `a` does not live long enough 错误的原因。

我也用Option::map清理了build方法。我将它重命名为 build 因为 builders 通常应该有一个 build 方法,除非有更明显的动词要使用(比如 open)。

另请参阅: