简体   繁体   中英

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

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 eg 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();

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()
    }
}

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();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM