如何在Rust中将模式匹配委托给函数?

fcwjkofz  于 2023-01-05  发布在  其他
关注(0)|答案(3)|浏览(100)

我有这样一种类型:

#[derive(PartialEq, Eq, Debug, Clone)]
enum MyEnum {
    ValueOne,
    ValueTwo,
    Integer(i32),
    Text(String),
}

在我的代码中,我有很多类似的模式:

let value = match iterator.next() {
    Some(MyEnum::ValueOne) => MyEnum::ValueOne,
    Some(value) => return Err(format!("Unexpected value {:?}", value)),
    None => return Err("Unexpected end of input!"),
}

或者这个:

let value = match iterator.next() {
    Some(MyEnum::Integer(i)) => MyEnum::Integer(i),
    Some(value) => return Err(format!("Unexpected value {:?}", value)),
    None => return Err("Unexpected end of input!"),
}

我想创建一个泛型函数take_value,我可以在其中指定所需的MyEnum类型,它返回Result
我只能用这样简单的值来解它:

fn take_value(iterator: &mut Iterator<MyEnum>, expected: MyEnum) -> Result<MyEnum, String> {
    match iterator.next() {
        Some(expected) => Ok(expected),
        Some(value) => Err(format!("Unexpected value {:?}", value)),
        None => Err("Unexpected end of input!"),
    }
}

它可以这样称呼:let value = take_value(iterator, MyEnum::ValueOne)?;
但是如何修改这个函数,使它可以被MyEnum::Integer调用,而不指定其中的整数值呢?

ttcibm8c

ttcibm8c1#

你不能用一个函数来做,但是下面的宏很接近你想要的,因为我们必须区分模式:pat和表达式:expr,你必须重复这一部分。

#[derive(PartialEq, Eq, Debug, Clone)]
enum MyEnum {
    ValueOne,
    ValueTwo,
    Integer(i32),
    Text(String),
}
macro_rules! take_value {
    ($iterator:expr, $pattern:pat, $expr:expr) => {
        match $iterator.next() {
            Some($pattern) => Ok($expr),
            Some(value) => Err(format!("Unexpected value {:?}", value)),
            None => Err("Unexpected end of input!".to_string()),
        }
    }
}

fn main() {
    let mut it = [MyEnum::ValueOne, MyEnum::ValueTwo, MyEnum::Integer(5)].into_iter();
    dbg!(take_value!(&mut it, MyEnum::ValueOne, MyEnum::ValueOne));
    dbg!(take_value!(&mut it, MyEnum::ValueOne, MyEnum::ValueOne));
    dbg!(take_value!(&mut it, MyEnum::Integer(i), MyEnum::Integer(i)));
    dbg!(take_value!(&mut it, MyEnum::ValueOne, MyEnum::ValueOne));
}

outputs

…
src/main.rs:21] take_value!(& mut it, MyEnum :: ValueOne, MyEnum :: ValueOne) = Ok(
    ValueOne,
)
[src/main.rs:22] take_value!(& mut it, MyEnum :: ValueOne, MyEnum :: ValueOne) = Err(
    "Unexpected value ValueTwo",
)
[src/main.rs:23] take_value!(& mut it, MyEnum :: Integer(i), MyEnum :: Integer(i)) = Ok(
    Integer(
        5,
    ),
)
[src/main.rs:24] take_value!(& mut it, MyEnum :: ValueOne, MyEnum :: ValueOne) = Err(
    "Unexpected end of input!",
)
vxbzzdmp

vxbzzdmp2#

你不能用这种“示例值”来做这件事,你需要给你的take_value函数传递一个 predicate 回调:

fn take_value<F: Fn(&MyEnum) -> bool>(iterator: &mut dyn Iterator<Item = MyEnum>, predicate: F) -> Result<MyEnum, String> {
    match iterator.next() {
        Some(value) if predicate (&value) => Ok(value),
        Some(value) => Err(format!("Unexpected value {:?}", value)),
        None => Err("Unexpected end of input!".to_string()),
    }
}

你可以这样称呼它

let value1 = take_value (&mut it, |v| v == &MyEnum::ValueOne);
let integer = take_value (&mut it, |v| matches!(v, MyEnum::Integer (_)));

Playground
或者,您可以使用一个简单的宏来隐藏 predicate 是回调的事实:

macro_rules! mk_pred {
    ($pat:pat) => {
        |v| matches!(v, $pat)
    }
}

它允许像这样调用take_value

let value1 = take_value (&mut it, mk_pred!(MyEnum::ValueOne));
let integer = take_value (&mut it, mk_pred!(MyEnum::Integer (_)));

Playground

lztngnrs

lztngnrs3#

我经常看到的一种方法,即使它有一些样板,也只是为每个枚举变量创建一个转换函数。

impl MyEnum {
    pub fn as_value_one(&self) -> Result<(), Error> {
        match self {
            MyEnum::ValueOne => Ok(()),
            _ => Err(self.unexpected_value()),
        }
    }

    pub fn as_integer(&self) -> Result<i32, Error> {
        match self {
            MyEnum::Integer(i) => Ok(*i),
            _ => Err(self.unexpected_value()),
        }
    }

    fn unexpected_value(&self) -> Error {
        format!("Unexpected value {:?}", self)
    }
}

用法:

let value = iterator.next()
    .ok_or_else(|| "Unexpected end of input!".to_owned())?
    .as_value_one()?;
let value: i32 = iterator.next()
    .ok_or_else(|| "Unexpected end of input!".to_owned())?
    .as_integer()?;

我将Option放在实现之外,因为它似乎不属于这里,但是您可以按照自己的意愿来做。

相关问题