不同类型的同一臂上的模式匹配 [英] Pattern matching on same arm with different types

查看:38
本文介绍了不同类型的同一臂上的模式匹配的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当两个或多个不同的枚举类型具有相同的数据成员或相同的功能时,我想知道是否有一种方法可以简化以下模式匹配臂.

I wonder if there is a way to simplify the following pattern match arms when two or more different enum's types have the same data member or same function.

(如果不是,最好解释一下原因)

(if not it will be nice to explain why)

根据要求提供我想要的更准确示例(请原谅我将数据成员访问与函数混淆)(在线试用):

as requested a more accurate example of what i want (forgive me for confusing data member access with function) (try it online):

struct Point<T> {
    x: i32,
    y: T,
}

enum Record {
    V4(Point<i64>),
    V6(Point<i32>),
}

fn get_record() -> Record {
    Record::V4(Point{ x: 1, y: 1})
}

fn main() {
    let x = match get_record() {
        Record::V4(r) => r.x,
        Record::V6(r) => r.x,
    };
    println!("{}", &x);

    // this will not compile
    // let rec = get_record();
    // println!("{}", rec.x);

    // this will not compile either
    // note: if V4 Point was i32 it will compile & run
    // let rec = get_record();
    // let x = match get_record() {
    //     Record::V4(r) | Record::V6(r) => r.x,
    // };
}

原帖:

use std::net::IpAddr;
use std::str::FromStr;

fn main() {
    let v4_or_v6 = IpAddr::from_str("1.2.3.4").unwrap();

    // match expression, both arms only differ by 1 char
    let s = match v4_or_v6 {
        IpAddr::V4(ip) => ip.to_string(),
        IpAddr::V6(ip) => ip.to_string(),
    };
    println!("{}", &s);

    // not working:
    // let s2 = match v4_or_v6 {
    //     IpAddr::V4(ip) | IpAddr::V6(ip) => ip.to_string(),
    // };
    // println!("{}", &s2);
}

我知道对 to_string() 的底层调用对 Ipv4 的实现与 Ipv6 不同,但我认为编译器可以足够聪明处理这个(我错了吗?)

I understand that the underlying call to to_string() has different implementation for Ipv4 than Ipv6 but i think the compiler can be smart enough to handle this (am i wrong?)

尝试使用注释掉的代码进行编译会导致编译错误(在线试用):

trying to compile with the commented out code results in compilation error (try it online):

Compiling playground v0.0.1 (/playground)

error[E0308]: mismatched types
  --> src/main.rs:16:37
   |
16 |         IpAddr::V4(ip) | IpAddr::V6(ip) => ip.to_string(),
   |                                     ^^ expected struct `std::net::Ipv4Addr`, found struct `std::net::Ipv6Addr`
   |
   = note: expected type `std::net::Ipv4Addr`
              found type `std::net::Ipv6Addr`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.
error: Could not compile `playground`.

推荐答案

工作代码去糖:

let s = match v4_or_v6 {
    IpAddr::V4(ip) => <Ipv4Addr as ToString>::to_string(&ip),
    IpAddr::V6(ip) => <Ipv6Addr as ToString>::to_string(&ip),
};

尽管语句看起来相同,但它们是不同的功能,并且在每个分支中静态知道将使用哪个 to_string.要使其在单个匹配臂中工作,您必须以某种方式从模式匹配中生成一个特征对象,以便每个 ip 具有相同的类型(即 &dyn ToString).目前没有办法做到这一点,我也没有看到任何类似的提案.

Even though the statements look the same, they are different functions and in each branch it is known statically which to_string is going to be used. To get this to work in a single match arm, you would have to somehow produce a trait object from the pattern match, so that each ip has the same type (i.e. &dyn ToString). Currently there isn't a way to do that and I haven't seen any proposal like it.

即使在 rustc 项目中,看到相同的 match arm 是很常见的,其中在每个 match arm 上调用相同的 trait 方法.目前就是这样.

It's pretty common to see identical-looking match arms, where the same trait method is called on each, even in the rustc project. This is just how it is, for now.

如果您有一个 enum,其中每个变体都包含实现相同特征的类型,那么在 enum 上实现特征并委托给内部类型可能会很方便.如果您没有特征但您的类型具有公共结构(如更新帖子结构中的 xy 字段),那么您可以提供一个enum 上的访问器:

If you have an enum where each variant holds types that implement the same traits, it might be convenient to implement the traits on the enum and delegate to the inner types. If you don't have a trait but your types have common structure (as in the x, y fields in the struct of your updated post), then you can provide an accessor on the enum:

impl Record {
    fn x(&self) -> i32 {
        match self {
            Record::V4(Point { x, .. }) => *x,
            Record::V6(Point { x, .. }) => *x,
        }
    }
}

虽然这基本上是一样的,但这意味着你可以一次编写它而不是在你需要访问x的任何地方:

While this is basically the same thing, it means you can write it once instead of everywhere that you need to access x:

let rec = get_record();
let x = get_record().x();

请注意,IpAddr 已经这样做了,在您的原始代码中,您可以完全避免 match:

Note that IpAddr already does this so, in your original code, you could have avoided the match altogether with:

let s = v4_or_v6.to_string();

这篇关于不同类型的同一臂上的模式匹配的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆