Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get active value from union-style enum in generic method

I have code similar to the following:

enum Value {
    Bool(bool),
    Int(i32),
    Float(f32),
    Str(String),
}

fn get_value(key: &str) -> Value {
    // read value from file
    match key {
        "b" => Value::Bool(true),
        "i" => Value::Int(666),
        "f" => Value::Float(42.),
        "s" => Value::Str("😈".to_string()),
         _  => panic!("Key {} not found.", str),
    }
}

fn convert<T>(e: &Value) -> T {
    // what to put here?
}

fn query<T>(t: &str) -> T {
    // … validation etc.
    convert::<T>(&get_value(t))
}

fn main() {
    let i = query::<i32>("i");
}

I.e. I need to query some values from a text file. query takes a type parameter and a string key parameter. It then returns the value associated to that key in the text file (if the type parameter and the type of the value don't match, simply panic!). Value and get_value are from a library in the actual code.

However I'm facing a problem when trying to convert a Value instance to the type it holds. If I try to do it with a simple match, I get

error: mismatched types: expected T, found x

where x is one of bool/i32/f32/String.

What is the right way to do this in Rust?

like image 898
emlai Avatar asked Dec 11 '22 21:12

emlai


1 Answers

Here there is a possible solution:

enum Value {
    Bool(bool),
    Int(i32),
    Float(f32),
    Str(String),
}

fn get_value(key: &str) -> Value {
    // read value from file
    match key {
        "b" => Value::Bool(true),
        "i" => Value::Int(666),
        "f" => Value::Float(42.),
        "s" => Value::Str("😈".to_string()),
         _  => panic!("Key {} not found.", key),
    }
}

trait ConversionTrait {
    type Output;

    fn convert(v: &Value) -> Option<Self::Output> {
        None
    }
}

impl ConversionTrait for i32 {
    type Output = i32;

    fn convert(v: &Value) -> Option<Self::Output> {
        match (*v) {
            Value::Int(x) => Some(x),
            _ => None
        }
    }
}

fn convert<T>(e: &Value) -> Option<T> where T : ConversionTrait<Output = T> {
    T::convert(e)
}


fn query<T>(t: &str) -> Option<T> where T : ConversionTrait<Output = T> {
    // … validation etc.
    convert::<T>(&get_value(t))
}

fn main() {
    let i = query::<i32>("i");
    // let j = query::<f32>("i"); ConversionTrait not implemented
    println!("{:?}", i);
}

First of all the convert and query methods could fail, so it's better they return an Option which can be None in case of failure.

Second, in Rust there is no generic specialization at the moment so a possible solution is to define a trait to do the conversion then implement the trait only for the types you want the conversion. (with generic specialization you would implement different version of the convert function)

Each implementation of the ConversionTrait above should extract the proper value from the Value object and return it. I implemented only the i32 version for reference.

like image 128
eulerdisk Avatar answered Jan 28 '23 03:01

eulerdisk