How to accept multiple enum types

Here I have a PizzaFactory which lets you add either Veggie or a Meat topping(s). I will need to preserve what kind of Veggie or Meat is added.

I discovered that you cannot nest enums so I went with the code below:

/**
Create a program that solves the following requirements:
>lets use create a pizza
>pizza can either be "Personal" or "Family" size with a price of $9 and $18 respectively
>ability to add veggie or meat toppings: veggie toppings at 5% to the base price while meat toppings as 10% to the base price
>veggie toppings are Mushrooms, Green Peppers, Onions, Tomatoes, and Pineapple
>meat toppings are Sausage, Chicken, Pepperoni, and Bacon
>you should be able to get the price of a pizza. e.g. a personal pizza with mushroom and sausage is $9 * (1 + (.05 + .10)) or $10.35
>you should be able to get the description of the pizza at any time. e.g. "A Personal Pizza with Mushrooms and Sausage"
 */

enum PizzaSize {
    Personal,
    Family,
}

enum Veggies {
    Mushrooms,
    GreenPeppers,
    Onions,
    Tomatoes,
    Pineapples,
}

enum Meats {
    Sausage,
    Chicken,
    Pepperoni,
    Bacon,
}

enum PizzaToppings {
    Veggies,
    Meats,
}

struct Pizza {
    size: PizzaSize,
    topping: Vec<PizzaToppings>,
}

impl Pizza {
    pub fn price(&self) -> f32 {
        self.topping.iter().fold(
            match self.size {
                PizzaSize::Personal => 9.,
                PizzaSize::Family => 18.,
            },
            |total, topping| match topping {
                PizzaToppings::Veggies => total * 1.05,
                PizzaToppings::Meats => total * 1.1,
            },
        )
    }
}

struct PizzaFactory {
    pizza: Pizza,
}

impl PizzaFactory {
    pub fn new(size: PizzaSize) -> Self {
        Self {
            pizza: Pizza {
                size: size,
                topping: vec![],
            },
        }
    }

    pub fn with(mut self, topping: PizzaToppings) -> Self {
        self.pizza.topping.push(topping);
        Self { pizza: self.pizza }
    }

    pub fn bake(&self) -> Pizza {
        self.pizza
    }
}

fn main() {
    let pizza = PizzaFactory::new(PizzaSize::Personal).with(PizzaToppings::Meats::Chicken).with(Veggies::GreenPeppers).bake();
}

Playground link: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=41ef4731c4e7b18400c44decf4ed1d56

Note that the with method in the PizzaFactory can either take a Veggie or a Meat topping. Now the compiler complains that the method requires a PizzaTopping type but it fails to understand that it composes the Veggies and the Meats enums described above it.

How can I inherit or compose other enums?

I can, obviously use traits but that'd mean a lot of boilerplate code and enums/variants make more sense here imo.

Perhaps the with method should accept a typeclass that is satisfied by both Veggie and the Meat enum. I don't know how to express that in Rust.

This isn't correct - you can nest any type within an enum variant like so:

enum PizzaToppings {
    Veggies(Veggies),
    Meats(Meats),
}

You can then match on the top level enum to get the nested value out:

match topping {
    PizzaToppings::Veggies(veggie) => { /* do something with veggie */ },
    PizzaToppings::Meats(meat) => { /* do something with meat */ },
}

See Chapter 6.1 of the book for info on the different ways you can embed data in an enum. There's three different styles of variant, which you can mix and match within a single enum:

enum Message {
    Quit,                        // Empty variant
    Move { x: i32, y: i32 },     // Struct-like variant
    Write(String),               // Tuple-like variant
    ChangeColor(i32, i32, i32),  // Tuple-like variant with multiple items
}
6 Likes

Maybe like this?

#[derive(Debug)]
enum PizzaSize {
    Personal,
    Family,
}

#[derive(Debug)]
enum Veggies {
    Mushrooms,
    GreenPeppers,
    Onions,
    Tomatoes,
    Pineapples,
}

#[derive(Debug)]
enum Meats {
    Sausage,
    Chicken,
    Pepperoni,
    Bacon,
}

#[derive(Debug)]
enum PizzaToppings {
    Veggies(Veggies),
    Meats(Meats),
}

#[derive(Debug)]
struct Pizza {
    size: PizzaSize,
    topping: Vec<PizzaToppings>,
}

impl Pizza {
    pub fn price(&self) -> f32 {
        self.topping.iter().fold(
            match self.size {
                PizzaSize::Personal => 9.,
                PizzaSize::Family => 18.,
            },
            |total, topping| match topping {
                PizzaToppings::Veggies(_) => total * 1.05,
                PizzaToppings::Meats(_) => total * 1.1,
            },
        )
    }

    pub fn with(mut self, topping: PizzaToppings) -> Self {
        self.topping.push(topping);
        self
    }

    pub fn new(size: PizzaSize) -> Self {
        Self {
            size: size,
            topping: vec![],
        }
    }
}

fn main() {
    let pizza = Pizza::new(PizzaSize::Personal).with(PizzaToppings::Meats(Meats::Chicken)).with(PizzaToppings::Veggies(Veggies::GreenPeppers));
    println!("Finished pizza: {:?} for ${}", pizza, pizza.price());
}