Match-statement to manipulate two variables at once?

Hello

I have a struct with two fields:

#[derive(Deserialize, Serialize)]
pub struct Voucher {
...
    distributorvoucher: Option<DistributorVoucher>,
    supervoucher_distributor: Option<Distributor>,
...

}

Either distributorvoucher or supervoucher_distributor has a value. Always exactly one of those field has a value, the other one is None.

I have written the following function to easily check if the Voucher is a 'supervoucher':

impl Voucher {
    fn is_supervoucher(&self) -> bool {
        self.distributorvoucher.is_none()
    }
}

When inserting the Voucher into a database I need to know which field to insert a NULL.

I can successfully do it this way:

let distributorvoucher_id = match &voucher.is_supervoucher() {
        true => Some(voucher.distributorvoucher.as_ref().unwrap().id),
        false => None,
    };
    let supervoucher_distributor = match &voucher.is_supervoucher() {
        true => Some(voucher.supervoucher_distributor.as_ref().unwrap().id),
        false => None,
    };

However, I was wondering if there is a shorter solution. I fabricated the following which seems to work:

let (distributorvoucher_id, supervoucher_distributor) = match &voucher.is_supervoucher() {
        true => (Some(voucher.distributorvoucher.as_ref().unwrap().id), None),
        false => (
            None,
            Some(voucher.supervoucher_distributor.as_ref().unwrap().id),
        ),
    };

I am, however, not sure this is valid code. Is this the right way to do it? Thanks!

The shortest way would be to use Option::map():

let distributorvoucher_id = voucher.distributorvoucher.as_ref().map(|dv| dv.id);
let supervoucher_distributor = voucher.supervoucher_distributor.as_ref().map(|svd| svd.id);

A longer way would be to explicitly match on the two fields:

let distributorvoucher_id = match &voucher.distributorvoucher {
    Some(dv) => Some(dv.id),
    None => None,
};
let supervoucher_distributor = match &voucher.supervoucher_distributor {
    Some(svd) => Some(svd.id),
    None => None,
};

Or to assert that exactly one has a value:

let (distributorvoucher_id, supervoucher_distributor) = match (
    &voucher.distributorvoucher,
    &voucher.supervoucher_distributor,
) {
    (Some(dv), None) => (Some(dv.id), None),
    (None, Some(svd)) => (None, Some(svd.id)),
    _ => unreachable!(),
};
1 Like

If feasible, I would replace the two fields with an enum.

enum SomeName {
    Distribution(DistributionVoucher),
    Super(Distributor),
}

impl SomeName {
    fn id(&self) -> u64 {
        match self {
            SomeName::Distribution(d) => d.id,
            SomeName::Super(s) => s.id,
        }
    }

    fn is_supervoucher(&self) -> bool {
        matches!(self, SomeName::Super(_))
    }

    // ...
}
5 Likes