1 个不稳定版本
0.0.1 | 2023年9月27日 |
---|
#38 in #finite
18KB
333 行
nicer_builder
本存储库旨在开发一个更智能且更了解现有构建上下文的构建宏。
观察
考虑以下Rust结构体
struct User {
name: &'static str,
age: Option<u32>,
address: Option<&'static str>,
}
现在,假设你想为这个结构体实现构建模式;它可以如下构建
impl User {
pub fn builder() -> UserBuilder {
UserBuilder::default()
}
}
#[derive(Default)]
pub struct UserBuilder {
name: Option<&'static str>,
age: Option<u32>,
address: Option<&'static str>,
}
impl UserBuilder {
pub fn name(mut self, name: &'static str) -> Self {
self.name = Some(name);
self
}
pub fn age(mut self, age: u32) -> Self {
self.age = Some(age);
self
}
pub fn address(mut self, address: &'static str) -> Self {
self.address = Some(address);
self
}
pub fn build(self) -> Result<User, &'static str> {
let name = self.name.ok_or("Name is required")?;
Ok(User {
name,
age: self.age,
address: self.address,
})
}
}
之后可以像这样使用
let user = User::builder()
.name("Alice")
.age(30)
.address("Wonderland")
.build()
.unwrap();
println!("User: {:?}", user);
无论你是否喜欢这种做法,它都带来了几个挑战
构建方法返回一个 Result
尽管构建器在技术上知道其状态,但我们被迫返回由构建器构建的 User
实例,并将其包装在 Result
中
构建器不了解已设置的字段
这导致以下方法序列完全可行
let user = User::builder()
.name("Alice")
.age(30)
.age(20)
.address("Wonderland")
.age(333)
.build()
.unwrap();
不理想的IDE补全支持
这个问题源于上一个问题;当我输入
let user = User::builder()
.name("Alice")
.
在接收到此 .
输入时,语言服务器协议 (LSP
) 提出了完整的构建器方法列表 - name
、age
和 address
,尽管 name
已经设置。此外,build
方法的可见性不受限制 - 你可以在构建过程中调用它,这可能导致在尝试 unwrap
结果时引发 panic
。
手动实现
因此,向 User
结构体添加新字段将需要在 Builder
端进行相应的调整。
解决方案?
本存储库通过提供一个 proc_macro,自动为结构体生成构建器实现,从而解决了这些问题。
#[derive(nicer_builder::Builder)]
struct User {
name: &'static str,
age: Option<u32>,
address: Option<&'static str>,
}
let alice = User::builder()
.with_address("SF")
.with_age(10)
.with_name("alice")
.build();
值得注意的是,构建方法不再返回一个 Result
;相反,它直接返回实际的 User
实例,并且保证在编译时不会失败。
缺点
此crate提供的 proc_macro
实际上生成一个完整的状态机。该机器的每个节点都包含一个专用的子构建器实现,定义它自己的方法集。更简单地说,proc_macro
会生成大约 2^(字段数)
个新结构体,这可能比预期更快地变得昂贵。
依赖关系
~0.5–1MB
~22K SLoC