Ph0N37Ic5
Ph0N37Ic5

Reputation: 13

Is there a way of calling a trait method on enum variants that have a field that implements the trait?

My enum has 40ish variants with about half of them implementing the trait, but here is a simpler example:

trait CheeseBoard {
    fn say_cheese(self);
}

struct Cheese {
    name: String,
}

impl CheeseBoard for Cheese {
    fn say_cheese(self) {
        println!("I am {}", self.name);
    }
}

struct Person {
    name: String,
}

impl CheeseBoard for Person {
    fn say_cheese(self) {
        println!("{} says cheese!", self.name);
    }
}

enum CheesyPerson {
    Cheese(Cheese),
    Person(Person),
    UncheesyNonperson,
}

fn main() {
    let _a = [
        CheesyPerson::Cheese(Cheese {
            name: "Gouda".into(),
        }),
        CheesyPerson::Person(Person {
            name: "Peer".into(),
        }),
        CheesyPerson::UncheesyNonperson,
    ];
    todo!("Call say_cheese on items in _a where the enum variant has exactly one field that implements the CheeseBoard trait.")
}

Upvotes: 0

Views: 1567

Answers (2)

Ruxo
Ruxo

Reputation: 359

Thank @cdhowie, your comment saves me from another hours of torment!

In case anyone wraps the enum with another layer of struct, ensure that you borrow the enum, not copy it. For example,

struct RoomForOne(CheesyPerson);

impl RoomForOne {
    fn get_cheese_board(&self) -> Option<&dyn CheeseBoard> {
        match &self.0 { // & here is important!
            CheesyPerson::Cheese(v) => Some(v),
            CheesyPerson::Person(v) => Some(v),
            _ => None,
        }
    }
}

Upvotes: 0

cdhowie
cdhowie

Reputation: 169008

This isn't directly possible in Rust; it has no mechanism to even match on "any enum possibility that has a single value," let alone that implements a particular trait.

The cleanest way I can think of to implement this is with a helper method that gives back a Option<&dyn CheeseBoard>:

impl CheesyPerson {
    fn get_cheese_board(&self) -> Option<&dyn CheeseBoard> {
        match self {
            Self::Cheese(v) => Some(v),
            Self::Person(v) => Some(v),
            _ => None,
        }
    }
}

Now you can do something like this:

for v in _a.iter().filter_map(|v| v.get_cheese_board()) {
    v.say_cheese();
}

Note this requires changing your CheeseBoard::say_cheese method because right now it takes self by value, consuming the CheeseBoard in the process. The method needs to take self by reference.

trait CheeseBoard {
    fn say_cheese(&self);
    //            ^
    // Add this to take self by reference
}

(Playground)

Upvotes: 1

Related Questions