DennyHiu
DennyHiu

Reputation: 6130

How to implement a function for different vector of structs with traits?

I have 3 Vec<T> structs that shared the same function:

Vec<RawTransaction>
Vec<RawCashTransaction>
Vec<RawAdjustmentTransaction>

All three shared the same VerifyableRaw traits and the verify() function. I use the verify() function check the validity of the content of that array/vector.

Here's my implementation. As you can see, all of them shared the same basic fields, namely: date, total, credit, and debit.

My problem is: since I use the same fields that those structs shared, the verify() function is the same for all of them. In verify function, I need to access the date, total, credit, and debit fields so I just copy and paste the code from one implementation to another.

My question is: Can I refactor this trait implementation into a single function definition ?

I found out that I need to repeat myself each time I need to use verify() function and VerifyableRaw trait to another struct that needs it

pub struct RawTransaction {
    pub date: Option<NaiveDate>,
    pub contact_name: String,
    pub total: Option<Decimal>,
    pub credit: String,
    pub debit: String,
}

pub struct RawCashTransaction{
    pub tr_type: String,
    pub date: Option<NaiveDate>,
    pub contact_name: String,
    pub total: Option<Decimal>,
    pub credit: String,
    pub debit: String,
}

pub struct RawAdjustmentTransaction{
    pub date: Option<NaiveDate>,
    pub info: String,
    pub total: Option<Decimal>,
    pub credit: String,
    pub debit: String,
}

Here's my trait implementation:

#[async_trait]
pub trait VerifyableRaw {
    async fn verify(&self, cid: String, db: Database) -> Result<bool, Err>;
}

#[async_trait]
impl VerifyableRaw for Vec<RawTransaction> {
    async fn verify(&self, cid: String, db: Database) -> Result<bool, Err> {
        /// .... this function is the same for all three

        let data = &self; // access the vector
        for (i, row) in data.iter().enumerate() {
            // enumerate each item in this vector
            let date = row.date.unwrap(); // check if the date is valid, etc
            let de = row.debit.clone(); // check if this value is valid
            let cr = row.credit.clone(); // check if this value is valid
            //  ... another process here ...
        }
    }
}

#[async_trait]
impl VerifyableRaw for Vec<RawCashTransaction> {
    async fn verify(&self, cid: String, db: Database) -> Result<bool, Err> {
        /// .... this function is exactly the same as RawTransaction above
    }
}

#[async_trait]
impl VerifyableRaw for Vec<RawAdjustmentTransaction> {
    async fn verify(&self, cid: String, db: Database) -> Result<bool, Err> {
        /// .... this function is exactly the same as RawTransaction above
    }
}

Upvotes: 0

Views: 86

Answers (2)

Jmb
Jmb

Reputation: 23453

To avoid repetition, you can put the common fields in a struct, and instead of copying every field into each concrete struct, simply include a single field whose type is the common one:

pub struct BaseTransaction {
    pub date: Option<NaiveDate>,
    pub total: Option<Decimal>,
    pub credit: String,
    pub debit: String,
}

pub struct RawTransaction {
    pub base: BaseTransaction,
    pub contact_name: String,
}

pub struct RawCashTransaction{
    pub base: BaseTransaction,
    pub tr_type: String,
    pub contact_name: String,
}

pub struct RawAdjustmentTransaction{
    pub base: BaseTransaction,
    pub info: String,
}

Then impl AsRef<BaseTransaction> for each concrete type:

impl AsRef<BaseTransaction> for RawTransaction {
    fn as_ref (&self) -> &BaseTransaction {
        &self.base
    }
}
impl AsRef<BaseTransaction> for RawCashTransaction {
    fn as_ref (&self) -> &BaseTransaction {
        &self.base
    }
}
impl AsRef<BaseTransaction> for RawAdjustmentTransaction {
    fn as_ref (&self) -> &BaseTransaction {
        &self.base
    }
}

and now you can have a single generic function that operates on AsRef<BaseTransaction>:

impl<T: AsRef<BaseTransaction>> VerifyableRaw for Vec<T> {
    async fn verify(&self, cid: String, db: Database) -> Result<bool, Err> {
        let base: BaseTransaction = self.as_ref();
        unimplemented!()
    }
}

Upvotes: 4

Maxim Gritsenko
Maxim Gritsenko

Reputation: 2592

Yes, you can:

pub trait VerifyableRaw {
    fn date(&self) -> Option<NativeDate>;
    fn credit(&self) -> &str;
    fn debit(&self) -> &str;
}

pub trait VerifyableRaws {
    async fn verify(&self, cid: String, db: Database) -> Result<bool, Err>;
}

impl VerifyableRaw for RawCashTransaction {
  fn date(&self) -> Option<NativeDate> { self.date }
  fn credit(&self) -> &str { &self.credit }
  fn debit(&self) -> &str { &self.debit }
}

impl<T: VerifyableRaw> VerifyableRaws for Vec<T> {
    async fn verify(&self, cid: String, db: Database) -> Result<bool, Err> {
        // your implementation here, but replace field access with method calls
    }
}

// other types have the same implementation

You still have to write implementation for the access method for every type, but I believe it's better than duplicate complex business logic.

If you have too many types like that and you don't want to write getters manually for each of them it's quite trivial to write a macro that will do it for you. But for just 3 types I would not recommend it for macros make things much more complicated than they need to be.

Upvotes: 1

Related Questions