【一起学Rust | 进阶篇】使用Bon快速生成builder,提高代码质量
在 Rust 编程语言中,除了广为人知的单例模式之外,工厂模式也是极为容易见到的一种设计模式。工厂模式在 Rust 中不仅出现频率高,而且还是官方大力推荐的写法。无论是 Rust 的标准库,还是众多的第三方库中,工厂模式都十分常见。在以往进行编程的时候,如果想要实现工厂模式,通常需要编写一个结构体,接着再去实现新的方法以及构建方法。然而,现在有厉害的开发者发布了 Bon 库。这个库能够让实现工厂模
文章目录
前言
在 Rust 编程语言中,除了广为人知的单例模式之外,工厂模式也是极为容易见到的一种设计模式。工厂模式在 Rust 中不仅出现频率高,而且还是官方大力推荐的写法。无论是 Rust 的标准库,还是众多的第三方库中,工厂模式都十分常见。
在以往进行编程的时候,如果想要实现工厂模式,通常需要编写一个结构体,接着再去实现新的方法以及构建方法。然而,现在有厉害的开发者发布了 Bon 库。这个库能够让实现工厂模式变得非常轻松,只需要在你的代码中添加相应的注解,就能够自动生成代码。这样一来,就可以省去大量的时间。
bon
是一个非常实用的 Rust 库。这个库主要用于为函数和结构体生成一种在编译时就能进行检查的构建器。通过使用“bon”库,开发人员可以更加高效地构建函数和结构体,并且在编译阶段就能够发现潜在的错误,从而提高代码的质量和可靠性。此外,“bon”库还为函数和方法提供了带有可选参数和命名参数的惯用部分应用。这种特性使得函数和方法的调用更加灵活,可以根据不同的需求进行参数的选择和组合,进一步增强了 Rust 代码的可扩展性和可维护性。
类似于 Java 中的 Lombok 库,当你在项目中使用了它之后,类的属性的 getter 和 setter 等功能便无需你亲自去编写了。而这里提到的“bon”,它的作用在于使得 builder 不用你自己去编写。Lombok 库为 Java 开发者带来了极大的便利,通过简化代码的编写过程,提高了开发效率。同样,“bon”在特定的场景下也发挥着重要的作用,为开发者省去了编写 builder 的繁琐工作,让开发过程更加流畅和高效。
一、安装Bon
将此内容添加到你的Cargo.toml
以使用这个库:
[dependencies]
bon = "2.2.1"
在非标准环境(no_std environments)中,可以通过将 default-features 设置为 false 来选择不使用 std 和 alloc cargo 特性。
或者在你的项目下执行以下命令
cargo add bon
版本用最新的就好了,这里只是个示例。
二、使用步骤
1.为方法实现builder
bon
可以通过构建器将带有位置参数的函数转换为带有 “具名” 参数的函数。只需在函数上方放置 #[builder]
宏即可轻松实现。这意味着使用 bon
这个工具,对于原本使用位置参数的函数,可以通过特定的方式(添加 #[builder]
宏)将其转换为使用具名参数的函数形式,使得函数调用更加清晰和灵活。例如,原本可能需要按照特定顺序传入参数的函数,现在可以通过指定参数名称来传入参数(链式调用)。
use bon::builder;
// 只需要下面一行注解
#[builder]
fn greet(name: &str, age: u32) -> String {
format!("Hello {name} with age {age}!")
}
// 就可以实现链式调用
let greeting = greet()
.name("Bon")
.age(24)
.call();
assert_eq!(greeting, "Hello Bon with age 24!");
这玩意儿也可以用在异步函数、可能出错的函数、泛型函数、“impl Trait”环境下,如果有问题你可以到git官方提交一个issue.
2.实现关联方法的builder
你也可以为关联方法生成构建器。要实现这一点,你还需要在 impl
代码块的上方添加一个 #[bon]
宏。
use bon::bon;
struct Counter {
val: u32,
}
#[bon] // 在impl代码块上需要添加这个宏
impl Counter {
#[builder] // 只要加了这个标记,调用时都是支持链式调用的
fn new(initial: Option<u32>) -> Self {
Self {
val: initial.unwrap_or_default(),
}
}
#[builder]
fn increment(&mut self, diff: u32) {
self.val += diff;
}
}
let mut counter = Counter::builder()
.initial(3)
.build();
counter
.increment()
.diff(3)
.call();
assert_eq!(counter.val, 6);
在遵循 Rust 构建器的常见命名约定时,bon 对在 impl 代码块内部名为 new 的方法进行特殊处理。它会生成名称稍有不同的函数。
如果 #[builder] 被放置在名为 new 的方法上,那么生成的函数将通过builder()
调用
对于不是名为 “new” 的任何其他方法以及任何自由函数通过call()
调用。
3. 为结构体实现builder
bon
支持经典模式,即使用 #[derive (Builder)]
语法对结构体进行标注以生成构建器。这意味着在 Rust 语言中,使用名为 bon 的 crate 时,可以通过在结构体上添加#[derive (Builder)]
属性来自动生成一个构建器。这个构建器可以用于以一种更加灵活和可读的方式创建结构体的实例。例如,可以逐步设置结构体的各个字段,而不是一次性提供所有的参数。这样可以提高代码的可读性和可维护性,并且可以在构建过程中进行一些额外的验证或处理。例如
use bon::Builder;
#[derive(Builder)] // 添加这一行就够了
struct User {
id: u32,
name: String,
}
let user = User::builder()
.id(1)
.name("Bon".to_owned())
.build();
assert_eq!(user.id, 1);
assert_eq!(user.name, "Bon");
在结构体上使用 #[derive (Builder)] 会生成构建器 API,这个 API 与在结构体的 new () 方法上放置 #[builder] 属性且该方法的签名与结构体的字段相似的情况完全兼容。
一般来说,在结构体上使用 #[derive(Builder)] 和在函数/方法上使用 #[builder] 具有几乎相同的应用程序接口(API)。在整个文档中会同时使用这两种方式来提供示例。如果示例中只展示了一种语法的用法(例如 #[builder]),那么在没有明确说明的情况下,另一种语法(例如 #[derive(Builder)])很可能具有类似的效果。
#builder
和#[derive(Builder)]
生成的构建器使用类型状态模式来确保所有必需的参数都被填充,并且不会重复调用相同的设置器,以防止意外的覆盖和拼写错误。如果有问题,将会产生编译错误。在构建器内部不存在潜在的panic
和unwrap()
调用。
4. Option<T>
字段成为可选项
如果你的函数参数或结构体字段(简称成员)是 Option类型,那么生成的构建器不会强制为这个成员设置值,默认值为 None。
它还会生成两个设置器:一个接受 T 类型的值,另一个接受 Option类型的值。第一个设置器在调用处避免用 Some()包裹值。第二个设置器允许直接传入 Option类型的值。
use bon::Builder;
#[derive(Builder)]
struct Projection {
x: Option<u32>,
y: Option<u32>,
// 对非`Option`类型的成员使用注解。
#[builder(default)]
z: u32,
}
// “x”和“y”都将被设置为“None”,“z”将被设置为“0”。
Projection::builder().build();
Projection::builder()
// 不使用`Some()`包裹值进行传递。
.x(10)
// 或者使用以“maybe_”为前缀的设置器,该设置器接受“Option”类型。
.maybe_y(Some(20))
// 对于`#[builder(default)]`和`Option<T>`生成的应用程序编程接口(API)是等效的。
// 当调用`build()`时,`z`将被设置为`0`。
.maybe_z(None)
.build();
5. 实现Into
转化
如果你的成员类型是 String 或者 PathBuf,并且你需要将它们设置为硬编码的字符串字面量,那么你必须编写.to_owned() 或者.to_string() 或者.into()。
然而,你可以使用 bon 生成接受 impl Into 的设置器,以消除手动转换的需要。这可以通过对单个成员使用 #[builder (into)] 进行配置,或者对多个成员同时使用 #[builder (on ({类型}, into))] 进行配置。
use bon::Builder;
use std::path::PathBuf;
// 所有类型为`String`的成员的设置器都将接受`impl Into<String>`。
#[derive(Builder)]
#[builder(on(String, into))]
struct Project {
name: String,
description: String,
// 此成员的唯一设置器将接受“实现了 Into<PathBuf> 的类型”。
#[builder(into)]
path: PathBuf,
}
Project::builder()
// “&str”在内部被转换为“String”。
.name("Bon")
.description("Awesome crate 🐱")
// “&str”在内部被转换为“PathBuf”。
.path("/path/to/your/heart")
.build();
这只是 bon 所提供功能的一部分。你可以考虑阅读指南部分的其余内容,以充分发挥 bon 的强大功能并理解其做出的决策。
参考文档
更多推荐
所有评论(0)