使用 Builder 模式时,我应该按值还是按可变引用获取 `self`?

Should I take `self` by value or mutable reference when using the Builder pattern?

到目前为止,我在官方 Rust 代码和其他 crate 中看到了两种构建器模式:

impl DataBuilder {
    pub fn new() -> DataBuilder { ... }
    pub fn arg1(&mut self, arg1: Arg1Type) -> &mut Builder { ... }
    pub fn arg2(&mut self, arg2: Arg2Type) -> &mut Builder { ... }
    ...
    pub fn build(&self) -> Data { ... }
}
impl DataBuilder {
    pub fn new() -> DataBuilder { ... }
    pub fn arg1(self, arg1: Arg1Type) -> Builder { ... }
    pub fn arg2(self, arg2: Arg2Type) -> Builder { ... }
    ...
    pub fn build(self) -> Data { ... }
}

我正在写一个新的 crate,我有点困惑应该选择哪种模式。我知道如果我以后改一些API会很痛苦,所以我想现在就做决定。

我理解它们之间的语义差异,但在实际情况下我们应该更喜欢哪一个?或者我们应该如何选择它们?为什么?

从同一个构建器构建多个值是否有益?

  • 如果是,使用&mut self
  • 如果没有,使用self

考虑 std::thread::Builder which is a builder for std::thread::Thread。它在内部使用 Option 字段来配置如何构建线程:

pub struct Builder {
    name: Option<String>,
    stack_size: Option<usize>,
}

它使用 self.spawn() 线程,因为它需要 name 的所有权。理论上它可以在字段外使用 &mut self.take() 名称,但随后对 .spawn() 的调用不会产生相同的结果,这是一种糟糕的设计。它可以选择 .clone() 名称,但随后会产生额外的且通常不需要的成本来生成线程。使用 &mut self 是有害的。

考虑 std::process::Command which serves as a builder for a std::process::Child。它具有包含程序、参数、环境和管道配置的字段:

pub struct Command {
    program: CString,
    args: Vec<CString>,
    env: CommandEnv,
    stdin: Option<Stdio>,
    stdout: Option<Stdio>,
    stderr: Option<Stdio>,
    // ...
}

它使用 &mut self.spawn(),因为它 而不是 获取这些字段的所有权来创建 Child。无论如何,它必须在内部将所有数据复制到 OS,因此没有理由使用 self。使用相同的配置生成多个子进程也有明显的好处和用例。

考虑 std::fs::OpenOptions which serves as a builder for std::fs::File。它只存储基本配置:

pub struct OpenOptions {
    read: bool,
    write: bool,
    append: bool,
    truncate: bool,
    create: bool,
    create_new: bool,
    // ...
}

它使用 &mut self.open(),因为它不需要拥有任何东西就可以工作。它有点类似于线程构建器,因为有一个与文件相关联的路径,就像有一个与线程相关联的名称一样,但是,文件路径仅传递给 .open() 而不会与构建器一起存储.有一个使用相同配置打开多个文件的用例。


上面的考虑实际上只涵盖了 .build() 方法中 self 的语义,但有充分的理由表明,如果您选择一种方法,您也应该将其用于临时方法:

  • API一致性
  • (&mut self) -> &mut Self 链接到 build(self) 显然不会编译
  • (self) -> Self 用于 build(&mut self) 会限制构建器的灵活性以供长期重复使用