如何在Rust中使用链式方法调用编写符合惯用法的构建模式?

4
基于以下示例,可以在Rust中编写具有链接方法调用的构建器模式,该模式可以通过值或引用(使用生命周期说明符)传递。 在Rust中,构建器模式可能看起来像这样:
 ui::Button::new()
    .label("Test")
    .align(Align::Center)
    .build();

在写Rust语言时,是否有一种比另一种更受欢迎的习惯用法?

有没有一些好的示例可以展示如何用Rust编写这个东西?


2
虽然我不是建造者模式的粉丝,但我会遵循Rust书籍中的简单方法。这相当符合惯用法。 - ljedrz
同样不是特别喜欢使用关键字风格的 Python 代码,例如:ui.button(label="Test", align='CENTER') 风格,因此正在寻找 Rust 中类似的东西。 - ideasman42
@ideasman42:Rust 有 Button { label = "Test", align = "CENTER", .. Default::default() } 语法 :) - Matthieu M.
是的,我强烈考虑使用这种语法风格...(实际上将在另一个问题中询问),顺便问一下,难道不应该是:Button { label: "Test", ....吗? - ideasman42
@MatthieuM。在这里提问:https://dev59.com/klgR5IYBdhLWcg3wLa28 - ideasman42
@ideasman42:刚刚点赞了它 ;) - Matthieu M.
2个回答

8

实际上有两个权衡考虑:

  • 命名的setter应该按值或引用接受self
  • 最终的build方法应该按值或引用接受self

我的建议是:

  • 对于setter使用可变引用
  • 对于build方法使用值传递

这与Rust Book中介绍的Builder Pattern略有不同,后者在build中使用引用。


为什么在设置器中使用可变引用传递?

虽然编译器可能会优化调用fn label(self, &str) -> ButtonBuilder所导致的移动,但并不保证。

另一方面,可变引用方式已经是最优的,因此您无需依赖于优化器。


为什么对于最终的build要进行传值操作?

对于只由Copy类型字段组成的构建器,build使用self&self没有区别。

但是,一旦构建器包含非-Copy类型字段,则将&self传递给build需要深度克隆这些字段。

另一方面,将self作为值传递可以使build移动这些字段,避免不必要的复制。

如果想要重复使用构建器,则构建器应该实现Clone


4

我看到构建者模式主要是通过获取Builder的所有权来修改它,以引用方式使用build()。例如:

#[derive(Debug, Eq, PartialEq)]
struct Foo {
    value: usize,
}

struct FooBuilder {
    foos: usize,
    bars: usize,
}

impl FooBuilder {
    fn new() -> FooBuilder {
        FooBuilder {
            foos: 0,
            bars: 0,
        }
    }
    fn set_foos(mut self, foos: usize) -> FooBuilder {
        self.foos = foos;
        self
    }
    fn set_bars(mut self, bars: usize) -> FooBuilder {
        self.bars = bars;
        self
    }
    fn build(&self) -> Foo {
        Foo {
            value: self.foos + self.bars,
        }
    }
}

fn main() {
    let foo = FooBuilder::new()
        .set_foos(2)
        .set_bars(3)
        .build();
    assert_eq!(foo, Foo { value: 5 });
}

在 Rust Playground 上试一试

这样做可以简化链接,并允许构建器的重复使用。

1
相反,该书使用可变引用来传递构建器(没有生命周期),请参见构建器模式。我认为这是为了提高效率。 - Matthieu M.
2
噢,有趣的是...我也不同意(与书和你)将&self传递给build。在FooBuilder中放置一个String,并且您将被迫在build中进行clone,即使没有人想要重用构建器的实例...这显然是浪费的。 - Matthieu M.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接