在泛型方法中从联合式枚举中获取活动值
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!
)。 Value
和 get_value
来自实际代码中的库。
但是,当我尝试 convert
一个 Value
实例到它所拥有的类型时,我遇到了一个问题。如果我尝试用简单的 match
来做,我会得到
error: mismatched types: expected T
,
found x
其中 x
是 bool
/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);
}
首先,convert
和 query
方法可能会失败,所以它们最好是 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
我有类似下面的代码:
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!
)。 Value
和 get_value
来自实际代码中的库。
但是,当我尝试 convert
一个 Value
实例到它所拥有的类型时,我遇到了一个问题。如果我尝试用简单的 match
来做,我会得到
error: mismatched types: expected
T
, foundx
其中 x
是 bool
/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);
}
首先,convert
和 query
方法可能会失败,所以它们最好是 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 typeSelf