36

I read through the trait documentation and found a neat definition for using traits on structs. Is it possible to use traits on enum types? I have seen answers that say no, but they are 3 years old and don't quite do what I'm trying to do.

I tried to do this:

#[derive(Debug, Copy, Clone)]
pub enum SceneType {
    Cutscene,
    Game,
    Menu,
    Pause,
    Credits,
    Exit,
}

//We want to guarantee every SceneType can be played statically
trait Playable {
    fn play();
}

impl Playable for SceneType::Cutscene {
    fn play() {}
}
error[E0573]: expected type, found variant `SceneType::Cutscene`
  --> src/main.rs:16:19
   |
16 | impl Playable for SceneType::Cutscene {
   |                   ^^^^^^^^^^^^^^^^^^^
   |                   |
   |                   not a type
   |                   help: you can try using the variant's enum: `SceneType`

I don't understand this error because the enum it references is in the same file. If I really can't use traits on enum variants, is there any way I can guarantee any enum trait must implement certain methods?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Vergil
  • 399
  • 1
  • 3
  • 7

3 Answers3

34

Can traits be used on enum types?

Yes. In fact, you already have multiple traits defined for your enum; the traits Debug, Copy and Clone:

#[derive(Debug, Copy, Clone)]
pub enum SceneType

The problem is that you aren't attempting to implement Playable for your enum, you are trying to implement it for one of the enum's variants. Enum variants are not types.

As the error message tells you:

help: you can try using the variant's enum: `SceneType`
impl Playable for SceneType {
    fn play() {}
}

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 8
    But if I want implement Playable for all enum variants ( Cutscene.play(), Game.play(), etc), what should I do? Add `match` expression to SceneType.play() method? – rusnasonov Feb 17 '20 at 07:47
  • 2
    @rusnasonov yes. – Shepmaster Feb 17 '20 at 16:21
  • 3
    Isn't that weird of the language since it allows enums of different data types. So not allowing trait on enum is a bit counter-intuitive. Thoughts? – Coder Nov 05 '21 at 00:41
4

If you want to implement a trait for Playable (i.e. for all enum variants) then the answer is quite simply: Yes you can. And Shepmaster's answer details how to do that.

However, if you really only want one enum variant to be Playable and not the others, then Rust doesn't directly support that, but there's an idiom I've seen used to emulate it. Instead of

enum MyEnum {
  A(i32, i32),
  B(String),
}

you explicitly implement each enum variant as a separate struct, so

enum MyEnum {
  A(A),
  B(B),
}

struct A {
  x: i32,
  y: i32,
}

struct B {
  name: String,
}

And then you can impl Playable for A without impl Playable for B. Whenever you want to call it, pattern match the MyEnum and, if you get an A, you can call play in your example on the result of the pattern match.

I don't recommend using this pattern for every enum you write, as it does make the code a decent bit more verbose and requires some boilerplate constructor methods to make it palatable. But for complicated enums with a lot of options, this sort of pattern can make the code easier to reason about, especially if you have a lot of traits or functions that only really apply to a couple of the enum possibilities.

Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116
2

Edit: Truly apologize; this answer isn't about

every SceneType can be played statically


Old answer

Try generics:

#[derive(Debug, Copy, Clone)]
pub enum SceneType <Cutscene>
where
    Cutscene: Playable
{
    Cutscene(Cutscene),
    Game,
    Menu,
    Pause,
    Credits,
    Exit,
}

//We want to guarantee every SceneType can be played statically
// Notice: add `pub` as enum
pub trait Playable {
    fn play();
}

// create struct for inner of SceneType::Cutscene 
struct Cutscene {
    // ...
}

// impl to specific Cutscene
impl Playable for Cutscene {
    fn play() {}
}

Test it:

fn main () {
    let cutscene = Cutscene{};
    let scenetype = SceneType::Cutscene(cutscene);
}

A downside I realized is that the generics are static. When there are more than one generics for an enum, all generics must be specified.


enum E <A, B>
where
    A: SomeTrait1,
    B: SomeTrait2,
{
    Enum1(A),
    Enum2(B),
}

trait SomeTrait1 {}
trait SomeTrait2 {}

struct S1 {}
impl SomeTrait1 for S1{}

struct S2 {}
impl SomeTrait2 for S2{}

struct X1 {}
impl SomeTrait1 for X1{}

fn main () {
    // specify the generics
    E::<S1, S2>::Enum1(S1{});
    E::<X1, S2>::Enum1(X1{});

    //error[E0282]: type annotations needed
    //  --> src/main.rs:26:5
    //   |
    //33 |     E::Enum1(S1{});
    //   |     ^^^^^^^^ cannot infer type for type parameter `B` declared on the enum `E`
    // E::Enum1(S1{});
    // E::Enum1(X1{});
}
BingLi224
  • 452
  • 2
  • 6
  • 12