Extract values from enum, use as trait?

I'm using serde to deserialize items in an array to one of two types of struct. Both types implement a trait: Startable

If I want to start all of the deserialzed items in a loop, I'm currently doing something like:

    for item in status.items {
        match item {
            ItemEntry::Pump(mut p) => p.start(),
            ItemEntry::Servo(mut s) => s.start(),
        }
    }

Is there some cleaner way I can do this so that I can do something like the following?

    for item in status.items {
        item.start();
    }

This is the complete code I'm working with:

use std::fs;
use serde::Deserialize;

pub trait Startable {
    fn start(&mut self);
    fn stop(&mut self);
}

#[derive(Deserialize,Debug)]
pub struct Pump {
    speed: u32,
    running: bool
}

#[derive(Deserialize,Debug)]
pub struct Servo {
    speed: u32,
    running: bool
}

#[derive(Deserialize,Debug)]
#[serde(tag = "type")]
pub enum ItemEntry {
    Pump(Pump),
    Servo(Servo)
}

#[derive(Deserialize,Debug)]
pub struct Status {
    items: Vec<ItemEntry>
}

impl Startable for Pump {
    fn start(&mut self) {
        println!("Starting pump at speed: {}", self.speed);
        self.running = true;
    }
    fn stop(&mut self) {
        self.running = false;
    }
}

impl Startable for Servo {
    fn start(&mut self) {
        println!("Starting servo at speed: {}", self.speed);
        self.running = true;
    }
    fn stop(&mut self) {
        self.running = false;
    }
}

fn main() {
    let data = fs::read_to_string("./status.json")
        .expect("Unable to read file");

    let status: Status = serde_json::from_str(&data)
        .expect("JSON does not have correct format.");

    for item in status.items {
        match item {
            ItemEntry::Pump(mut p) => p.start(),
            ItemEntry::Servo(mut s) => s.start(),
        }
    }
}

Example JSON file:

{
  "items": [
    {
      "type": "Pump",
      "speed": 1,
      "running": false
    },
    {
      "type": "Servo",
      "speed": 2,
      "running": false
    }
  ]
}

Currently, there's no way within the language semantics to express “Every variant of this enum implements this trait” and make use of that fact to avoid repetition. However, you can express the code repetition with a macro (specific to the trait):

macro_rules! item_enum {
    ($($variant:ident),*) => {
        #[derive(Deserialize,Debug)]
        #[serde(tag = "type")]
        pub enum ItemEntry {
            $( $variant($variant), )*
        }
        
        impl Startable for ItemEntry {
            fn start(&mut self) {
                match self {
                    $( ItemEntry::$variant(ref mut v) => v.start(), )*
                }
            }
            fn stop(&mut self) {
                match self {
                    $( ItemEntry::$variant(ref mut v) => v.stop(), )*
                }
            }
        }        
    };
}

item_enum!(Pump, Servo);

The enum_dispatch crate specifically does this for you.

2 Likes

enum_dispatch looks very useful, thanks

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.