Home > Net >  How do I implement a get-method for an enum that returns the right type?
How do I implement a get-method for an enum that returns the right type?

Time:02-02

I have an enum that holds variants which contain certain types. I want to write a get method that automatically returns the right type if the enum variant contains this type.

pub enum Var {
    Switch(bool),
    PositiveInteger(u32),
    Integer(i32),
    PositiveFloat(f64),
    Float(f64),
    Phase(f64), // a float x, where 0.0 <= x < 2Pi
    Turn(f64), // the same, but with 0.0 <= x < 1.0
}

Somewhere else in the code I want to get the value inside the variant e.g. PositiveFloat( value ). But it doesn't seem possible (to me) to write a generic function, like this:

impl Var {
    pub fn get<T>(&self) -> T 
where
    T: Default,
{
        match self {
            Var::Switch(value) => value,
            Var::PositiveInteger(value) => value,
            // etc
}

I instead seem to have to implement a different get-method for each variant of the enum: get_positive_integer(), get_integer(), etc. Maybe I am looking about the problem the wrong way. What I want to achieve something like this:

let positive_integer = Var::PositiveInteger(1);
let x: u32 = positive_integer.get();

let float = Var::Float(0.1);
let y: f64 = float.get();

CodePudding user response:

You can create a trait for that:

pub trait VarTypes: Sized {
    fn get(var: &Var) -> Option<Self>;
}

impl VarTypes for bool {
    fn get(var: &Var) -> Option<Self> {
        match *var {
            Var::Switch(v) => Some(v),
            _ => None,
        }
    }
}

impl VarTypes for u32 {
    fn get(var: &Var) -> Option<Self> {
        match *var {
            Var::PositiveInteger(v) => Some(v),
            _ => None,
        }
    }
}

impl VarTypes for i32 {
    fn get(var: &Var) -> Option<Self> {
        match *var {
            Var::Integer(v) => Some(v),
            _ => None,
        }
    }
}

impl VarTypes for f64 {
    fn get(var: &Var) -> Option<Self> {
        match *var {
            Var::PositiveFloat(v) | Var::Float(v) | Var::Phase(v) | Var::Turn(v) => Some(v),
            _ => None,
        }
    }
}

Then you can implement get() with it:

impl Var {
    pub fn get<T>(&self) -> T
    where
        T: VarTypes   Default,
    {
        T::get(self).unwrap_or_default()
    }
}

CodePudding user response:

You can't define the method in an impl Var directly because you'd need a different implementation for every type. What you can do instead is define a generic trait and implement that for every type you want:

trait Get<T> {
    fn get(&self) -> Option<T>;
}

impl Get<bool> for Var {
    fn get(&self) -> Option<bool> {
        match self {
            Var::Switch(value) => Some(*value),
            _ => None,
        }
    }
}

impl Get<u32> for Var {
    fn get(&self) -> Option<u32> {
        match self {
            Var::PositiveInteger(v) => Some(*v),
            _ => None,
        }
    }
}

impl Get<f64> for Var {
    fn get(&self) -> Option<f64> {
        match self {
            Var::PositiveFloat(v) | Var::Float(v) | Var::Phase(v) | Var::Turn(v) => Some(*v),
            _ => None,
        }
    }
}
// …

I changed the return type to Option<T> because that relaxes the requirement on T and is more in line like other similar methods (slice::get, ...) and you can regain your semantics with a simple unwrap_or_default():

let positive_integer = Var::PositiveInteger(1);
let x: u32 = positive_integer.get().unwrap_or_default();
  • Related