#builder #builder-pattern #state-machine #fields #generator #finite #finite-state-machine

builder-pattern-fsm

基于有限状态机的构建模式生成器

1 个不稳定版本

0.0.1 2023年9月27日

#38 in #finite

MIT 许可证

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) 提出了完整的构建器方法列表 - nameageaddress,尽管 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