Match enums with associated values

Hi All,

Hopefully a simple syntax question.
In a match, is there some way I can say "I don't care about the specific variant as long as it has an associated value"?

Given an enum like this:

enum Ex {
    VarOne,
    VarTwo,
    VarThree( AssocOne ),
    VarFour( AssocTwo ),
}

And assuming that AssocOne and AssocTwo both implement a trait Foo with function my_trait_func...

How do I do the match in the following?

impl Foo for Ex {
    fn my_trait_func( &self, x : something ) {
        match self {
              VarOne     => x.magic( "One" ),
              VarTwo     => x.magic( "Two" ),
              _( t )         => t.my_trait_func( x ),
        }
    }
}

The above gives me the error:
expected one of =>, if, or |, found (
If I change the _ to Ex I get the error:
expected tuple struct or tuple variant, found enum 'Ex'

Not really. You can combine multiple cases in a single arm though (if both t are the same type):

VarThree(t) | VarFour(t) => //do thing with t

Also, variants of an enum do not implement traits, the entire enum does.

Edit:
Also, unless you have use Ex::* somewhere, then your match arms must spell out the entire variant like this:

Ex::VarOne =>
Ex::VarTwo =>
Ex::VarThree(t) | Ex::VarFour(t) =>
1 Like

Thanks m51.

I wanted to avoid listing all the variants :frowning:
I realise that traits are at the enum level, it is the associated AssocOne and AssocTwo that implement the traits, not their variants.

It's a pity that this isn't supported, it seems like a fairly useful/common thing to do...

It's difficult without knowning what you are trying to accomplish, but you could do this:

use Ex::*;

fn main() {
    let mut e = Var3(Box::new(A{}));
    e.go();
    e = Var4(Box::new(B{}));
    e.go();
}

enum Ex{
    Var1,
    Var2,
    Var3(Box<Foo>),
    Var4(Box<Foo>)
}

impl Ex{
    fn go(&self) {
        match self{
            Var1 => (), //do something
            Var2 => (), //do something else
            Var3(f) | Var4(f) => f.bar()
        }
    }
}

trait Foo{
 fn bar(&self);
}

struct A;

impl Foo for A {
    fn bar(&self){
        println!("A")
    }
}

struct B;

impl Foo for B{
    fn bar(&self) {
        println!("B")
    }
}

Yes, but I had hoped that the pattern matching of match would mean I wouldn't need to list Var3 and Var4 explicitly and could instead do _(f) to match all variants with an associated value.

With out this, I have to add the variant to the enum and then also add it to impl. Whereas it should be easily provable at compile time that _(f) is legal if all variants have the right kind of f from a trait perspective... perhaps with the help of a where on the type of f

I think it again comes down to how trait objects are used in rust. By default all things are on the stack with a Known size. In your original example, the t doesn't have a Known size since it could be either AssocOne or AssocTwo.
Box<dyn Trait> puts the thing on the Heap.

You can write a method that extracts the variant argument, and then use that (untested):

impl Ex {
    #[inline(always)]
    fn arg_as_foo(&self)->Option<&dyn Foo> {
        match self {
            VarThree(x) => Some(&x),
            VarFour(x) => Some(&x)
            _ => None
        }
    }
}
            

impl Foo for Ex {
    fn my_trait_func( &self, x : something ) {
        match (self, self.arg_as_foo()) {
             (Ex::VarOne, _)  => x.magic( "One" ),
             (Ex::VarTwo, _)  => x.magic( "Two" ),
             (_, Some(t))     => t.my_trait_func(x)
             (_, None)        => unreachable!(),
        }
    }
}

Hi Zireael,

Good point. That was an oversight in my example, I apologise.
Those types are indeed Box<...> in the real code.