如何使用构建器模式构建 Rust API

面对现实吧; 凭借其可选和命名参数,Python 比许多其他编程语言(如 Rust)更具优势。然而,Rust 库作者可以使用构建器模式非常有效地解决这个缺点。这种模式背后的想法看似简单:创建一个可以但不需要保存所有值的对象,并让它在所有必需字段都存在时创建我们的类型。

在本文中,我们将探索 Rust 的构建器模式,30 个最佳 Windows 代码编辑器涵盖以下内容:

  • 建造者模式

  • 拥有与可变引用的构建器

  • Into和AsRef特质

  • 默认值

  • 使用类型状态跟踪集合字段

  • Rust 构建器模式板条箱

建造者模式

为了熟悉 Rust 中的构建器模式,修复无法在 Windows 10/11 上登录 YouTube让我们首先比较一下使用和不使用构建器时代码的外观:

// without a builder

let base_time = DateTime::now();

let flux_capacitor = FluxCapacitor::new();

let mut option_load = None;

let mut mr_fusion = None;

if get_energy_level(&plutonium) > 1.21e9 {

option_load = Some(plutonium.rods());

} else {

// need an energy source, can fail

mr_fusion = obtain_mr_fusion(base_time).ok();

}

TimeMachine::new(

flux_capacitor,

base_time,

option_load,

mr_fusion,

)

// with a builder

let builder = TimeMachineBuilder::new(base_time);

.flux_capacitor(FluxCapacitor::new());

if get_energy_level(&plutonium) > 1.21e9 {

builder.plutonium_rods(plutonium.rods())

} else {

builder.mr_fusion()

}

.build()

本书中的所有示例都设计得很简单。实际上,如何修复Windows 10中的系统中断100CPU?如何降低cpu使用率过高问题您会为具有更多依赖性的复杂类型使用构建器。

构建器的主要功能是将构建实例所需的数修复本地打印机在远程桌面中未显示打印机的问题据保存在一个地方。只需定义TimeMachineBuilder结构,放入一堆字段,添加一个with a和一个方法,以及一些 setter,就大功告成了。就是这样,您现在已经了解了有关构建器的所有信息。下次见!Option<_>implnewbuild

你还在这里?啊,我怀疑你不会上当。当然,如何修复 Microsoft Store 错误 0x80073CFB对于构建者来说,除了明显的数据收集之外,还有更多的东西。

拥有与可变引用的构建器

与某些垃圾收集语言不同,在 Rust 中,我们区分拥有的值和借用的值。因此,有多种方法可以设置构建器方法。一个通过可变引用获取,如何修复 Gmail 签名图片不显示的问题另一个通过值获取。前者有两个子变体,返回链接或 . 允许链接稍微更常见。&mut selfself&mut self()

我们的示例使用链接,因此使用按值生成器。的结果new直接用于调用另一个方法。

可变借用的构建器的好处是能够在同一个构建器上调用多个方法,同时仍然允许一些链接。但是,这是以需要对构建器设置进行绑定为代价的。例如,如何修复Zoom没有资格注册?Zoom没有资格注册错误及其背后的原因以下代码将因返回方法而失败:&mut self

let builder= ByMutRefBuilder::new()

.with_favorite_number(42); // this drops the builder while borrowed

然而,做完整的链仍然有效:

ByMutRefBuilder::new()

.with_favorite_number(42)

.with_favorite_programming_language("Rust")

.build()

如果我们想重用构建器,我们需要绑定调用的结果:new()

let mut builder = ByMutRefBuilder::new();

builder.with_favorite_number(42) // this returns `&mut builder` for further chaining

我们也可以忽略链接,而是多次调用相同的绑定:

let mut builder = ByMutRefBuilder::new();

builder.with_favorite_number(42);

builder.with_favorite_programming_language("Rust");

builder.build()

另一方面,按值构建器需要重新绑定才能不丢弃它们的状态:

let builder = ByValueBuilder::new();

builder.with_favorite_number(42); // this consumes the builder :-(

因此,它们通常被链接起来:

ByValueBuilder::new()

.with_favorite_number(42)

.with_favorite_programming_language("Rust")

.build()

因此,对于按值构建器,我们需要链接。另一方面,适用于 Windows 10 的 50 个最佳免费游戏下载可变引用的构建器将允许链接,只要构建器本身绑定到某个局部变量即可。此外,可变引用的构建器可以自由重用,因为它们不会被它们的方法消耗。

一般来说,链接是使用构建器的预期方式,所以这不是一个大的缺点。此外,根据构建器包含的数据量,移动构建器可能会在性能配置文件中可见,但是这种情况很少见。

如果构建器将经常用于具有许多分支的复杂代码,如果 Windows 10 一直锁定自己怎么办?或者它可能会从中间状态重用,我更喜欢可变引用的构建器。否则,我会使用按值生成器。

Into和AsRef特质

当然,构建器方法可以做一些基本的转换。最流行的一种使用Into特征来绑定输入。

例如,您可以将索引作为具有实现的任何内容,修复 PC 上 Google Chrome 高 CPU 和内存使用率的 12 种方法或者允许构建器通过具有参数来减少分配,这使得函数同时接受 a和. 对于可以作为引用给出的参数,特征可以在提供的类型中允许更多的自由。Into<usize>Into<Cow<'static, str>>&'static strStringAsRef

还有一些专门的特征,例如IntoIterator和 ,修复 Windows 10 中的 Skype 高 CPU 使用率ToString有时会很有用。例如,如果我们有一系列值,我们可以有add扩展add_all每个内部的方法Vec:

impl FriendlyBuilder {

fn add(&mut self, value: impl Into<String>) -> &mut Self {

self.values.push(value.into())

self

}

fn add_all(

&mut self,

values: impl IntoIterator<Item = impl Into<String>>

) -> &mut Self {

self.values.extend(values.into_iter().map(Into::into));

self

}

}

默认值

类型通常可以有可行的默认值。因此,修复 Windows 10 上出错的 Spotify 错误的 9 种方法构建器可以预先设置这些默认值,并且仅在用户请求时才替换它们。在极少数情况下,获得默认值可能代价高昂。构建器可以使用一个Option,它有自己的None默认值,或者执行另一个技巧来跟踪设置了哪些字段,我们将在下一节中解释。

当然,我们不依赖于Default实现给我们的任何东西;我们可以设置自己的默认值。例如,我们可以决定越多越好,因此默认数字将是而不是零。u32::MAXDefault

对于涉及引用计数的更复杂的类型,我们可能有一个static默认值。对于引用计数的少量运行时开销,它每次都会得到。或者,如果我们允许借用静态实例,修复 Outlook 错误的 11 种解决方案无法在阅读窗格中显示此项我们可以使用 默认值,避免分配,同时仍然保持构建简单:Arc::clone(_)Cow<'static, T>

use std::sync::Arc;

static ARCD_BEHEMOTH: Arc<Behemoth> = Arc::new(Behemoth::dummy());

static DEFAULT_NAME: &str = "Fluffy";

impl WithDefaultsBuilder {

fn new() -> Self {

Self {

// we can simply use `Default`

some_defaulting_value: Default::default(),

// we can of course set our own defaults

number: 42,

// for values not needed for construction

optional_stuff: None,

// for `Cow`s, we can borrow a default

name: Cow::Borrowed(DEFAULT_NAME),

// we can clone a `static`

goliath: Arc::clone(ARCD_BEHHEMOTH),

}

}

}

使用类型状态跟踪集合字段

跟踪设置字段仅适用于拥有的变体。这个想法是使用泛型将有关已设置哪些字段的信息放入类型中。因此,我们可以避免双重设置,事实上,我们甚至可以禁止它们,并且只有在设置了所有必填字段后才允许构建。

让我们以最喜欢的数字、编程语言和颜色为例,其中只需要第一个。我们的类型是,where表示是否设置了数字,是否设置了编程语言,是否设置了颜色。TypeStateBuilder<N, P, C>NPC

然后我们可以创建Unset和Set类型来填充我们的泛型。我们的new函数将返回,并且只有 a有一个方法。TypeStateBuilder<Unset, Unset, Unset>TypeStateBuilder<Set, _, _>.build()

在我们的示例中,我们到处都使用默认值,因为使用不安全代码无助于理解模式。但是,使用此方案当然可以避免不必要的初始化:

use std::marker::PhantomData;

/// A type denoting a set field

enum Set {}

/// A type denoting an unset field

enum Unset {}

/// Our builder. In this case, I just used the bare types.

struct<N, P, C> TypeStateBuilder<N, P, C> {

number: u32,

programming_language: String,

color: Color,

typestate: PhantomData<(N, P, C)>,

}

/// The `new` function leaves all fields unset

impl TypeStateBuilder<Unset, Unset, Unset> {

fn new() -> Self {

Self {

number: 0,

programming_language: "",

color: Color::default(),

typestate: PhantomData,

}

}

}

/// We can only call `.with_favorite_number(_)` once

impl<P, C> TypeStateBuilder<Unset, P, C> {

fn with_favorite_number(

self,

number: u32,

) -> TypeStateBuilder<Set, P, C> {

TypeStateBuilder {

number,

programming_language: self.programming_language,

color: self.color,

typestate: PhantomData,

}

}

}

impl<N, C> TypeStateBuilder<N, Unset, C> {

fn with_favorite_programming_language(

self,

programming_language: &'static str,

) -> TypeStateBuilder<N, Set, C> {

TypeStateBuilder {

number: self.number,

programming_language,

color: self.color,

typestate: PhantomData,

}

}

}

impl<N, P> TypeStateBuilder<N, P, Unset> {

fn with_color(self, color: Color) -> TypeStateBuilder<N, P, Set> {

TypeStateBuilder {

number: self.number,

programming_language: self.programming_language,

color,

typestate: PhantomData,

}

}

}

/// in practice this would be specialized for all variants of

/// `Set`/`Unset` typestate

impl<P, C> TypeStateBuilder<Set, P, C> {

fn build(self) -> Favorites {

todo!()

}

}

该界面的工作方式与按值构建器完全相同,但不同之处在于,如果为impl这些情况添加了 ,则用户只能设置一次或多次字段。我们甚至可以控制以何种顺序调用哪些函数。例如,我们只能允许after已经被调用,并且 typestate 编译为空。.with_favorite_programming_language().with_favorite_number()

这样做的缺点显然是复杂性;有人需要编写代码,而编译器必须对其进行解析和优化。因此,除非 typestate 用于实际控制函数调用的顺序或允许优化初始化,否则它可能不是一个好的投资。

Rust 构建器模式板条箱

由于构建器遵循如此简单的代码模式,因此在crates.io上有许多箱子可以自动生成它们。

derive_builder crate 使用参数和来自定义的可选默认值构建我们的标准可变引用构建器。您还可以提供验证功能。它是自动生成构建器的最流行的 proc macro crate,也是一个不错的选择。在撰写本文时,该板条箱已有六年历史,因此这是自派生稳定以来的第一个派生板条箱之一。Intostruct

typed -builder crate 处理整个按值类型状态实现,如上文所述,因此您可以忘记刚刚阅读的所有内容。只需在您的代码中键入并享受类型安全的构建器。它还具有默认值和可选注释,并且有一个注释允许您拥有一个始终采用任何值并设置的 setter 方法。cargo add typed-builderintostrip_optionSome(value)

safe -builder-derive crate 也实现了类型状态,但是,通过为每个设置/未设置字段的组合生成一个类型状态impl,它会导致代码呈指数增长。对于最多三个或四个字段的小型构建器,这可能仍然是一个可以接受的选择,否则,编译时间成本可能不值得。

tidy -builder crate 与 typed-builder 基本相同,但它用于typestate。buildstructor crate 也受到 typed-builder 的启发,但它使用带注释的构造函数而不是结构。builder -pattern crate 也使用类型状态模式,并允许您注释惰性默认值和验证函数。~const bool

毫无疑问,未来还会有更多。如果您想在代码中使用自动生成的构建器,我认为它们中的大多数都是不错的选择。与往常一样,您的里程可能会有所不同。例如,要求对Into参数进行注释对某些人来说可能更符合人体工程学,但对其他人来说却降低了复杂性。一些用例需要验证,而其他用例则没有用。

结论

构建器可以轻松弥补 Rust 中命名参数和可选参数的缺失,甚至可以在编译时和运行时自动转换和验证。此外,大多数开发人员都熟悉该模式,因此您的用户会有宾至如归的感觉。

与往常一样,缺点是需要维护和编译的额外代码。Derive crates 可以以另外一小部分编译时间为代价来消除维护负担。

那么,您应该为所有类型使用构建器吗?我个人只会将它们用于至少有五个部分或复杂的相互依赖关系的类型,但是,在这些情况下,我认为它们是不可或缺的。

猜你喜欢

转载自blog.csdn.net/weixin_47967031/article/details/130163116