Get active value from union style enum in general 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"); } 

those. I need to query some values ​​from a text file. query accepts a type parameter and a string key parameter. Then it returns the value associated with this key in the text file (if the type parameter and value type do not match, just panic! ). Value and get_value from the library in the actual code.

However, I encounter a problem when trying to convert a Value instance to the type that it contains. If I try to do this with a simple match , I get

error: inappropriate types: expected T , found x

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

What is the correct way to do this in Rust?

+4
source share
3 answers

Here 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 can fail, so it is better to return Option , which can be None in case of failure.

Secondly, in Rust there is no general specialization at the moment, so a possible solution is to define a sign for conversion, and then implement a sign only for the types that you want to convert. (with a general specialization you can implement a different version of the conversion function)

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

+6
source

Andrea P answer was really helpful. I adapted it a bit and made a macro to avoid code duplication in impl Convert s. Here, just in case, the result is of interest:

 trait Convert : Sized { fn convert(Value) -> Option<Self>; } macro_rules! impl_convert { ($t:ty, $id:ident) => ( impl Convert for $t { fn convert(v: Value) -> Option<$t> { match v { Value::$id(x) => Some(x), _ => None, } } } ) } impl_convert!(bool, Bool); impl_convert!(i32, Int); impl_convert!(f32, Float); impl_convert!(String, Str); fn query<T: Convert>(t: &str) -> T { // … validation etc. match T::convert(get_value(t)) { Some(x) => x, None => panic!("`{}` has an incorrect type", t), } } 

Convert inherits Sized to prevent:

warning : dash core::marker::Sized not implemented for type Self

+4
source

The short answer is that you cannot. Check out the function prototype:

 fn convert<T>(e: &Value) -> T 

This suggests that for any T that the caller selects, the function should return this. This will entail a very large number of features, including any type ever created by any user of this code.

However, there is a solution to your problem. You just need to see how the standard library implements str::parse :

 fn parse<F>(&self) -> Result<F, F::Err> where F: FromStr { FromStr::from_str(self) } 

FromStr is a true hero, and many implement it. Any type that implements FromStr can be used with parse .

I believe that you can use FromStr for your case, because your code does not make any sense. ^ _ ^ Your sample code:

 let i = query::<i32>("i"); 

Indicates the type twice - once as a parameter of type <i32> and once as a string "i" . This is rather strange, so I assume that in fact the argument is the name of the key-value pair. This makes me think about how the Rust Postgres box works (pseudocode shown):

 let id: i32 = row.get(0); let name: String = row.get(1); 

I believe that you can combine on FromStr and add some template:

 use std::collections::HashMap; use std::str::FromStr; struct ConfigFile { raw: HashMap<String, String>, } impl ConfigFile { fn read_from_disk() -> Self { let mut map = HashMap::new(); map.insert("name".into(), "Anna".into()); map.insert("points".into(), "210".into()); map.insert("debugging".into(), "true".into()); ConfigFile { raw: map } } fn get<T>(&self, name: &str) -> Option<T> where T: FromStr { self.raw.get(name).and_then(|v| v.parse().ok()) } } fn main() { let conf = ConfigFile::read_from_disk(); let n: String = conf.get("name").unwrap(); let p: i32 = conf.get("points").unwrap(); let d: bool = conf.get("debugging").unwrap(); println!("{} has {} points, {}", n, p, d); } 
+3
source

All Articles