Rust 中的構造器 - 2 自動生成構造器
類似於 Clone 和 Debug 的工作方式,crates 也可以創建自己的派生宏,有許多 crates 可以幫助我們生成構造器,可以查看#builder(https://lib.rs/keywords/builder)。讓我們來看看其中的一部分:
derive_builder
#[derive(Debug, derive_builder::Builder)]
#[builder(build_fn(validate = ["Self::validate"]))]
struct Query {
fields: Vec<String>,
text_filter: String,
database: String,
table: String,
fixed_amount: Option<usize>,
descending: bool,
}
// Usage same as described patterns:
let query = Query::builder()
.table("...".into())
// ...
.build()
.unwrap();
這個派生宏生成一個新的 struct,其名稱是在原結構體名字後面加了 Builder(本例中爲 QueryBuilder)。
derive_builder 的缺點是對整個對象進行驗證,而不是對每個字段進行驗證。構造的錯誤變體是 String,這使得匹配錯誤或返回錯誤數據比使用錯誤枚舉更難:
impl Query {
fn validate(&self) -> Result<(), String> {
let valid = self
.database
.as_ref()
.map(|value| value == "pg_roles")
.unwrap_or_default();
if valid {
Ok(())
} else {
Err("Cannot construct Query on 'pg_roles'".into())
}
}
}
typed-builder
Typed-builder 解決了 derive_builder 的問題。
使用 derive_builder,你可以設置一個字段兩次 (或多次):
Query::builder()
.database("imdb".into())
// ...
.database("fishbase".into())
它取最後一個 set 字段的值,它的缺點是擴展宏需要更長的時間,因爲要生成更多的宏。增加的複雜性也使構造器變得更加複雜。
Buildstructor
Buildstructor 是在 impl 塊上的註釋。它不是使用 struct 上的字段 (如前兩個所示) 來生成代碼,而是包裝現有的構造函數:
struct MyStruct {
sum: usize
}
#[buildstructor::buildstructor]
impl MyStruct {
#[builder]
fn new(a: usize, b: usize) -> MyStruct {
Self { sum: a + b }
}
}
MyStruct::builder().a(1).b(2).build();
與 type -builder 類似,它生成了構造器的中間 struct,其好處是在編譯時檢查所有字段是否存在。然而,這樣做的缺點是編譯時間較慢,傳遞時靈活性較差。
BuilderStructor 看起來與 Rust 語言更加兼容,允許它支持異步構建器!
其他構建方式
如果你只想構建一個有大量默認字段的 struct,使用..(基本語法) 和 Default trait(無論是自定義實現還是使用 #[derive(Default)] 的默認實現):
#[derive(Default)]
struct X {
a: u32,
b: i32,
c: bool,
}
X { a: 10, ..Default::default() }
如果你想要計算、約束、封裝和命名字段,你可以創建一箇中間 struct,它可以傳遞給構造函數:
struct Report {
title: String,
on: chrono[object Object]DateTime
// ...
}
struct ReportArguments {
title: String,
on: Option<chrono::DateTime>
// ...
}
impl Report {
fn new_from_arguments(ReportArguments { title, on }: ReportArguments) -> Result<Self, &str> {
if title.
.chars()
.all(|chr| matches!(chr, 'a'..='z' | '0'..='9'))
{
Ok(Self {
title,
on: chrono.unwrap_or_else(|| todo!())
})
} else {
Err("Invalid report name")
}
}
}
然而,這兩個都沒有使用良好的鏈式調用語法。
總結
構造器模式可以幫助我們編寫更清晰、更易讀的 api,它還可以幫助 api 的使用者編寫更好的代碼。我們可以應用約束來確保我們的 struct 被正確初始化,使用一個乾淨的 API 強制執行契約。
本文翻譯自:
https://www.shuttle.rs/blog/2022/06/09/the-builder-pattern
coding 到燈火闌珊 專注於技術分享,包括 Rust、Golang、分佈式架構、雲原生等。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/x7JU9ikj-KSXjzvu4A3rvQ