我在 Rust 应用程序中使用如下结构:
pub struct Invoice {
pub id: Uuid,
pub amount: i64,
pub rows: Vec<InvoiceRow>,
pub company: Option<Box<Company>>,
pub customer: Option<Box<Customer>>,
pub payments: Vec<Payment>,
// and many many others
}
pub struct Payment {
pub id: Uuid,
pub amount: i64,
pub invoice_id: Uuid,
pub payment_method_id: Uuid,
pub payment_method: Option<Box<PaymentMethod>>,
// and many many others
}
pub struct PaymentMethod {
pub id: Uuid,
pub name: String,
pub mode: String,
// and many many others
}
我有这样的方法:
fn payments(invoice: &Invoice) -> String {
let payments = invoice
.payments
.iter()
.fold(String::new(), |mut output, payment| {
let _ = write!(
output,
"mode: {mode} - amount: {amount}\n",
mode = payment.payment_method.as_ref().expect("payment_method").mode(),
amount = payment.amount(),
);
output
});
payments
}
在本例中,
Invoice
是通过数据库调用创建的,该调用可以基于如下代码查询payments
字段(Vec<Payment>
):
pub struct InvoiceFieldsToQuery {
pub rows: Option<Box<InvoiceRowFieldsToQuery>>,
pub company: Option<Box<CompanyFieldsToQuery>>,
pub customer: Option<Box<CustomerFieldsToQuery>>,
pub payments: Option<Box<PaymentFieldsToQuery>>,
// and many many others
}
let fields_to_query = InvoiceFieldsToQuery::default()
.query_company(Some(Box::new(
CompanyFieldsToQuery::default().query_address(),
)))
.query_customer(Some(Box::new(
CustomerFieldsToQuery::default().query_address(),
)))
.query_payments(Some(Box::new(
PaymentFieldsToQuery::default())
));
// and many many others
如您所见,我正在使用类似
.expect("payment_method")
的代码,但我不想使用。
有没有办法避免使用
.expect()
,使用某些东西在编译时检测和阻止没有字段的结构?
或者 - 更好 - 一种在编译时检测我需要使用
payments
(或 rows
等)及其 payment_method
的方法,所以我必须使用 .query_payments(Some(Box::new(PaymentFieldsToQuery::default())))
来查询它?
我需要这些字段为
Option
,因为我仅在需要时才查询它们。而且我并不总是需要它们。
例如,对于
Invoice
,有时我需要 rows
、payments
和 customer
,有时只需要 customer
。
这就是问题。
当我需要payments
时,我想在编译时
检测到我正在传递一个填充了
Invoice
字段的
payments
结构。我已准备好更改代码中的所有内容以实现此编译时检查!
这个想法是将
Option
替换为类型级别
Option
。即通过不同的类型区分
Some
和
None
。我们提供了一个特征来动态处理它们作为运行时
Option
,当您特别需要一种状态时,您可以通过类型来要求它。特点:
pub struct Missing;
pub struct Existing;
pub trait MaybeMissing {
type Repr<T: ?Sized>: MaybeMissingRepr<T>;
fn map<T: ?Sized, U: ?Sized>(
v: Self::Repr<T>,
f: impl FnOnce(Box<T>) -> Box<U>,
) -> Self::Repr<U>;
}
impl MaybeMissing for Existing {
type Repr<T: ?Sized> = Box<T>;
fn map<T: ?Sized, U: ?Sized>(
v: Self::Repr<T>,
f: impl FnOnce(Box<T>) -> Box<U>,
) -> Self::Repr<U> {
f(v)
}
}
impl MaybeMissing for Missing {
type Repr<T: ?Sized> = Missing;
fn map<T: ?Sized, U: ?Sized>(
_v: Self::Repr<T>,
_f: impl FnOnce(Box<T>) -> Box<U>,
) -> Self::Repr<U> {
Missing
}
}
pub trait MaybeMissingRepr<T: ?Sized> {
fn as_ref(&self) -> Option<&T>;
fn as_mut(&mut self) -> Option<&mut T>;
fn into_owned(self) -> Option<Box<T>>;
}
impl<T: ?Sized> MaybeMissingRepr<T> for Box<T> {
fn as_ref(&self) -> Option<&T> {
Some(&**self)
}
fn as_mut(&mut self) -> Option<&mut T> {
Some(&mut **self)
}
fn into_owned(self) -> Option<Box<T>> {
Some(self)
}
}
impl<T: ?Sized> MaybeMissingRepr<T> for Missing {
fn as_ref(&self) -> Option<&T> {
None
}
fn as_mut(&mut self) -> Option<&mut T> {
None
}
fn into_owned(self) -> Option<Box<T>> {
None
}
}
结构:
pub struct Invoice<
CompanyOpt: MaybeMissing,
CustomerOpt: MaybeMissing,
PaymentMethodOpt: MaybeMissing,
> {
pub id: Uuid,
pub amount: i64,
pub rows: Vec<InvoiceRow>,
pub company: CompanyOpt::Repr<Company>,
pub customer: CustomerOpt::Repr<Customer>,
pub payments: Vec<Payment<PaymentMethodOpt>>,
// and many many others
}
pub struct Payment<PaymentMethodOpt: MaybeMissing> {
pub id: Uuid,
pub amount: i64,
pub invoice_id: Uuid,
pub payment_method_id: Uuid,
pub payment_method: PaymentMethodOpt::Repr<PaymentMethod>,
// and many many others
}
pub struct PaymentMethod {
pub id: Uuid,
pub name: String,
pub mode: String,
// and many many others
}
DB查询方法:
pub struct InvoiceFieldsToQuery<
RowsOpt: MaybeMissing,
CompanyOpt: MaybeMissing,
CustomerOpt: MaybeMissing,
PaymentsOpt: MaybeMissing,
> {
pub rows: RowsOpt::Repr<InvoiceRowFieldsToQuery>,
pub company: CompanyOpt::Repr<CompanyFieldsToQuery>,
pub customer: CustomerOpt::Repr<CustomerFieldsToQuery>,
pub payments: PaymentsOpt::Repr<PaymentFieldsToQuery>,
// and many many others
}
impl Default for InvoiceFieldsToQuery<Missing, Missing, Missing, Missing> {
fn default() -> Self {
Self {
rows: Missing,
company: Missing,
customer: Missing,
payments: Missing,
// and many many others
}
}
}
impl<CompanyOpt: MaybeMissing, CustomerOpt: MaybeMissing, PaymentsOpt: MaybeMissing>
InvoiceFieldsToQuery<Missing, CompanyOpt, CustomerOpt, PaymentsOpt>
{
pub fn query_rows(
self, /* ... */
) -> InvoiceFieldsToQuery<Existing, CompanyOpt, CustomerOpt, PaymentsOpt> {
InvoiceFieldsToQuery {
rows: Box::new(/* query rows */),
company: self.company,
customer: self.customer,
payments: self.payments,
// and many many others
}
}
}
// Etc., more `query_` methods.
impl<
RowsOpt: MaybeMissing,
CompanyOpt: MaybeMissing,
CustomerOpt: MaybeMissing,
PaymentsOpt: MaybeMissing,
> InvoiceFieldsToQuery<RowsOpt, CompanyOpt, CustomerOpt, PaymentsOpt>
{
pub fn query(self) -> Invoice<CompanyOpt, CustomerOpt, PaymentsOpt> {
Invoice {
company: CompanyOpt::map(self.company, |company| /* query DB */),
// And more...
}
}
}
最后,需要值的方法:
fn payments<CompanyOpt: MaybeMissing, CustomerOpt: MaybeMissing>(invoice: &Invoice<CompanyOpt, CustomerOpt, Existing>) -> String {
let payments = invoice
.payments
.iter()
.fold(String::new(), |mut output, payment| {
let _ = write!(
output,
"mode: {mode} - amount: {amount}\n",
// No `expect()`!
mode = payment.payment_method.mode(),
amount = payment.amount(),
);
output
});
payments
}