Rust结构体使用具名参数
创建于 2024年7月25日修改于 2024年7月25日
考虑以下Rust代码段,一个 Target
结构体,以及一个相应的构造者 Builder
:
struct Target {
foo: bool,
bar: u64,
baz: i64,
}
struct Builder {
foo: Option<bool>,
bar: Option<u64>,
baz: Option<i64>,
}
impl Builder {
fn new() -> Builder {
return Builder {
foo: None,
bar: None,
baz: None,
};
}
fn foo(mut self, v: bool) -> Builder {
self.foo = Some(v);
return self;
}
fn bar(mut self, v: u64) -> Builder {
self.bar = Some(v);
return self;
}
fn baz(mut self, v: i64) -> Builder {
self.baz = Some(v);
return self;
}
fn build(self) -> Target {
return Target {
foo: self.foo.unwrap_or(false),
bar: self.bar.unwrap_or(0),
baz: self.baz.unwrap_or(0),
};
}
}
这段代码让你可以构建一个对象。
let t = Builder::new().foo(true).bar(2).baz(3).build();
不过,这并不算很有意思。除了必须表达的构造者之外,我们似乎并没有在表达我们的主要目标!
让我们采用一种构造模式(T
)来表达我们的主要目标,并且看起来像是通过具名参数进行赋值。
macro_rules! T {
($($n:ident = $e:expr),+) => {
Builder::new()$(.$n($e))*.build()
};
}
现在你在表达你的主要目标了。很多项目中它们高频使用,以至于值得用单字母的宏来表示:
let t = T!(foo=true, bar,2, baz=3);
这显得好一些。具名参数,让你知道自己传递了什么,虽然它底层还是一个构造者模式。
不像直接构造结构体,这种模式允许你只传递你需要的内容。
let t = T!(bar=2);
assert_eq!(
t,
Target {
foo: false,
bar: 2,
baz: 0
}
)
有人可能会说,Rust 中的许多构建器都是允许失败的。它们通常使用 Result
类型来完成构建。
让我们考虑一下这个问题:
struct TryBuilder {}
impl TryBuilder {
fn new() -> TryBuilder {
return TryBuilder {};
}
fn bar(mut self, v: u64) -> Self {
return self;
}
fn build(mut self) -> Result<Target, String> {
return Err("Couldn't build".to_string());
}
}
我们将“尝试”构建一个 T
,而不是直接构建:
macro_rules! tryT {
($($n:ident = $e:expr),+) => {
TryBuilder::new()$(.$n($e))*.build()?
};
}
上面的宏让我免费获得了类似异常处理的行为。省去一个 ?
号!
fn try_u64_from_t() -> Result<u64, String> {
let t = tryT!(bar = 3);
return Ok(t.bar);
}
tryT
自动用 ?
将表达式解包,所以得到的 t
已经被解包了。当然,这不算啥优点,因为 ?
本身已经非常好用且出色了!
业界一直有很多理由不推荐使用宏,人们认为它难以理解、不好调试等等。但我们都不好意思承认的最大原因只是品味:宏有它特定的风格,稍微设置不当就会让人感觉很不舒服。
但是如果你是在自己的代码库中工作,那就尽量满足自己,随心所欲吧!一个有很多宏的代码库并不是天生邪恶。毕竟我们不是在 C 语言中,这些东西还是相当有原则的。