有没有办法在编译时检测到我没有填充这些结构体的所需字段?

问题描述 投票:0回答:1

我在 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
结构。

我已准备好更改代码中的所有内容以实现此编译时检查!

rust
1个回答
0
投票
这是一个好主意,老实说我不知道它是否值得。但这是你的选择:)

这个想法是将

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 }
    
© www.soinside.com 2019 - 2024. All rights reserved.