Jonatan Kallus
Jonatan Kallus

Reputation: 283

Match on pair of enum when both are of the same kind

I want to get rid of the repeated code here, and not have to list each enum kind:

use Geometry::*;
let geometry = match (&self.geometry, &other.geometry) {
    (Point(a), Point(b)) => Point(*a.interpolate(b, t)),
    (Curve(a), Curve(b)) => Curve(*a.interpolate(b, t)),
    (EPBox(a), EPBox(b)) => EPBox(*a.interpolate(b, t)),
    (_, _) => unimplemented!("Kinds not matching")
};

The types inside the kinds all implement the Interpolate trait which has the interpolate method:

enum Geometry {
    Point(Point),
    Curve(Curve),
    EPBox(EPBox)
}

trait Interpolate {
    fn interpolate(&self, other: &Self, t: f64) -> Box<Self> {
        // ...
    }
}

impl Interpolate for Point {}
impl Interpolate for Curve {}
impl Interpolate for EPBox {}

What I want to do is something like this:

let geometry = match (&self.geometry, &other.geometry) {
    (x(a), x(b)) => x(*a.interpolate(b, t)), // and check that a and b impls Interpolate
    (_, _) => unimplemented!("Kinds not matching")
};

Upvotes: 3

Views: 515

Answers (3)

Jonatan Kallus
Jonatan Kallus

Reputation: 283

I came up with the following solution, which is easy to extend with new Interpolate types:

macro_rules! geometries {
    ( $( $x:ident ),+ ) => {
        enum Geometry {
        $(
            $x($x),
        )*
        }
        fn interpolate_geometries(a: &Geometry, b: &Geometry, t: f64) -> Geometry {
            use Geometry::*;
            match (a, b) {
            $(
                ($x(a), $x(b)) => $x(a.interpolate(b, t)),
            )*
                _ => unimplemented!("Kinds not matching")
            }
        }
        $(
        impl Interpolate for $x {}
        )*
    };
}

geometries!(Point, Curve, EPBox);

Which make it possible to later do:

let geometry = interpolate_geometries(&self.geometry, &other.geometry, t);

Thanks for all answers and comments! Especially the one with the binop macro, which is quite similar to my solution. After reading up on macros (thanks to comments here) I came up with this solution.

Upvotes: 0

Caesar
Caesar

Reputation: 8484

I'm not sure how many of these operations you want to implement, and how many enum variants you actually have. Depending on that, the best answer changes quite a bit, I think. If you really only have one operation (interpolate), and three variants: type it out, anything else will be a lot less simple and maintainable.

That being said, I might use

let geometry = binop!(match (&self.geometry, &other.geometry) {
    (a, b) => *a.interpolate(b, t)
});

based on

macro_rules! binop {
    (match ($av:expr, $bv:expr) { ($a:ident, $b:ident) => $op:expr }) => {{
        use Geometry::*;
        binop!($av, $bv, $a, $b, $op, Point, Curve, EPBox)
    }};
    ($av:expr, $bv:expr, $a:ident, $b:ident, $op:expr, $($var:path),*) => {
        match ($av, $bv) {
            $(($var($a), $var($b)) => $var($op),)*
            _ => unimplemented!("Kinds not matching")
        }
    };
}

Playground

You'll have to list up the enum variants once. If you don't want that, you'll have to go for a proc macro. I think that would be overkill.

Upvotes: 4

nxe
nxe

Reputation: 261

With your current code, you can't really do this. Checking if the enum variants are the same variant is fairly simple with the use of core::mem::discriminant, but you can't access the contained value without match or if let statements. What you can do is use type parameters for the variables and type IDs. This is a really tacky way to do things, and should be avoided really, but it works. I've given an example to show you how you could implement it

use std::any::Any;

trait SomeTrait {
    fn some_function(&self, other: Self) -> &Self;
}

#[derive(Clone)]
struct Var0 {
    eg: i64
}

#[derive(Clone)]
struct Var1 {
    other_eg: f64
}

fn check<T: 'static, B: 'static>(lhs: T, rhs: B) -> Option<T> {
    if lhs.type_id() == rhs.type_id() {
        Some(lhs.some_function(rhs))
    } else {
        None
    }
}

fn main() {
    let a = Var0{ eg: 2 };
    let b = Var0{ eg: 9 };
    let c = Var1 { other_eg: -3f64 };
    assert!(check(a.clone(), b).is_some());
    assert!(check(a, c).is_none());
}

impl SomeTrait for Var0 { /* ... */ }
impl SomeTrait for Var1 { /* ... */ }

Please note though: as I said before this is messy and pretty hacky and not very nice to read. If I were writing this I would use the match statement over this, but it's up to you what you do since this does require some reworking to implement. I'm also not sure if you can use different lifetimes for the check function other than `static which might be a problem.

Upvotes: 1

Related Questions