同一手臂上不同类型的图案匹配

时间:2018-12-16 12:29:35

标签: rust pattern-matching

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

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

更新:

根据要求提供了我想要的更准确的示例(请原谅我将数据成员访问与功能混为一谈)(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的实现不同,但我认为编译器可以足够聪明地处理此问题(对吗?)

尝试使用注释掉的代码进行编译会导致编译错误(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`.

1 个答案:

答案 0 :(得分:3)

工作代码对以下内容进行了糖化处理:

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)。目前尚无办法,而且我还没有看到任何类似的提案。

看到外观相同的匹配臂是很常见的,即使在rustc项目中,每个匹配臂也调用相同的特征方法。暂时就是这样。


如果您有一个enum,其中每个变量都包含实现相同特征的类型,则可以方便地在enum上实现特征并将其委托给内部类型。如果您没有特征,但是您的类型具有相同的结构(如更新后的帖子结构中的xy字段),则可以在{{1 }}:

enum

虽然这基本上是同一件事,但它意味着您只需编写一次即可,而不必在需要访问impl Record { fn x(&self) -> i32 { match self { Record::V4(Point { x, .. }) => *x, Record::V6(Point { x, .. }) => *x, } } } 的任何地方编写它:

x

请注意,let rec = get_record(); let x = get_record().x(); 已经这样做了,在您的原始代码中,您可以完全避免使用IpAddr

match