在泛型方法中从联合式枚举中获取活动值

Get active value from union-style enum in generic method

我有类似下面的代码:

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");
}

即我需要 query 文本文件中的一些值。 query 采用类型参数和字符串键参数。然后它 returns 文本文件中与该键关联的值(如果类型参数和值的类型不匹配,则简单地 panic!)。 Valueget_value 来自实际代码中的库。

但是,当我尝试 convert 一个 Value 实例到它所拥有的类型时,我遇到了一个问题。如果我尝试用简单的 match 来做,我会得到

error: mismatched types: expected T, found x

其中 xbool/i32/f32/String.

之一

在 Rust 中执行此操作的正确方法是什么?

简短的回答是你不能。检查你的函数原型:

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

这表示对于 调用者选择 的任何 T,该函数必须 return。这将带来非常多的可能性,包括此代码的任何用户创建的每种类型。

但是,对于您的问题,有类型 的解决方案。你只需要看看标准库是如何实现的 str::parse:

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

FromStr才是真正的英雄,很多类型都实现了它。任何实现 FromStr 的类型都可以与 parse.

一起使用

我相信您可以使用 FromStr 来处理您的情况,因为您的代码没有任何意义。 ^_^ 你的示例代码:

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

指定类型两次——一次作为类型参数<i32>,一次作为字符串"i"。这很奇怪,所以我猜测参数实际上是键值对的名称。这让我想起了 Rust Postgres crate 是如何工作的(显示伪代码):

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

我相信您可以利用 FromStr 并添加一些样板文件:

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

这里有一个可能的解决方案:

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

首先,convertquery 方法可能会失败,所以它们最好是 return 和 Option,在以下情况下可以是 None失败。

其次,在 Rust 中目前没有泛型特化,所以一个可能的解决方案是定义一个特征来进行转换,然后只为你想要转换的类型实现该特征。 (通过通用专业化,您将实现不同版本的 convert 函数)

上面 ConversionTrait 的每个实现都应该从 Value 对象和 return 对象中提取正确的值。 我只实现了i32版本供参考。

真的很有帮助。我对其进行了一些调整,还制作了一个宏来避免 impl Convert 中的代码重复。以下是结果,以防万一有人感兴趣:

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继承Sized防止:

warning: the trait core::marker::Sized is not implemented for the type Self