Reputation: 6130
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
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
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