使用构建器模式时,我应该按值还是可变引用获取“self”?

Spr*_*ite 13 design-patterns ownership rust

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

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 { ... }
}
Run Code Online (Sandbox Code Playgroud)
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 { ... }
}
Run Code Online (Sandbox Code Playgroud)

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

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

kmd*_*eko 13

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

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

考虑std::thread::Builder哪个是std::thread::Thread. 它Option在内部使用字段来配置如何构建线程:

pub struct Builder {
    name: Option<String>,
    stack_size: Option<usize>,
}
Run Code Online (Sandbox Code Playgroud)

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

考虑std::process::Command哪个充当std::process::Child. 它的字段包含程序、参数、环境和管道配置:

pub struct Command {
    program: CString,
    args: Vec<CString>,
    env: CommandEnv,
    stdin: Option<Stdio>,
    stdout: Option<Stdio>,
    stderr: Option<Stdio>,
    // ...
}
Run Code Online (Sandbox Code Playgroud)

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

考虑std::fs::OpenOptions哪个充当std::fs::File. 它只存储基本配置:

pub struct OpenOptions {
    read: bool,
    write: bool,
    append: bool,
    truncate: bool,
    create: bool,
    create_new: bool,
    // ...
}
Run Code Online (Sandbox Code Playgroud)

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


self上面的注意事项实际上只涵盖了方法中的语义.build(),但是有足够的理由表明,如果您选择一种方法,您也应该将其用于临时方法:

  • API一致性
  • 链接(&mut self) -> &mut Selfbuild(self)显然不会编译
  • 使用(self) -> Selfintobuild(&mut self)会限制构建器长期重用的灵活性

另请参阅:如何在 Rust 中使用链式方法调用编写惯用的构建模式?