Rust 枚举与模式匹配

定义枚举

枚举值

定义枚举类型:

enum IpAddrKind {
    V4,
    V6,
}

fn main() {
    let four = IpAddrKind::V4;
    let six = IpAddrKind::V6;

    route(IpAddrKind::V4);
    route(IpAddrKind::V6);
}

fn route(ip_kind: IpAddrKind) {}

定义关联数据的枚举类型:

fn main() {
    enum IpAddr {
        V4(String),
        V6(String),
    }

    let home = IpAddr::V4(String::from("127.0.0.1"));

    let loopback = IpAddr::V6(String::from("::1"));
}

定义关联不同数据类型的枚举类型:

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

这个枚举拥有4个内嵌了不同类型数据的变体:

  • Quit没有关联任何数据
  • Move包含了一个匿名结构体(不需要为结构体命名)
  • Write包含了一个String(允许是任何类型,包括结构体,但不知道是否允许包含枚举类型)
  • ChangeColor包含了3个i32值。(有点类似包含了元组)

枚举类型允许定义方法,语法与结构体相同。

fn main() {
    enum Message {
        Quit,
        Move { x: i32, y: i32 },
        Write(String),
        ChangeColor(i32, i32, i32),
    }

    impl Message {
        fn call(&self) {
            // method body would be defined here
        }
    }

    let m = Message::Write(String::from("hello"));
    m.call();
}

Option枚举及其在空值处理方面的优势

空值常常引发BUG,rust引入Option< T >强制用户判断是否存在空值。 首先Option是个枚举类型,< T >是泛型,之后讨论,但是其作用就是表示Option枚举中的Some变体可以表示任意类型的数据。

enum Option<T> {
    Some(T),
    None,
}

当数据可能存在空值时,我们使用Option来表示,Some表示有值,None表示空值(仅仅是表示空值)。注意,使用None时一定要告知类型,因为None不像Some可以推导类型。

    let some_number = Some(5);
    let some_string = Some("a string");
    let none_number:Option<i32>=None;

举个例子,如下x不可能存在空值,所以直接赋值,y可能存在空值,我们使用Option赋值。

    let x: i8 = 5;
    let y: Option<i8> = Some(5);

但是,x和y不能相加,因为他们拥有不同的类型。之所以这样,是rust需要强制你处理可能为空的值。

//错误代码
fn main() {
    let x: i8 = 5;
    let y: Option<i8> = Some(5);

    let sum = x + y;
}

所以在rust中,不为Option的值必定不为空。有Option的值有可能为空,强制你进行处理,更加安全。

需要使用match来处理Option的some和none,先了解以下match。

控制流运算符match

match比较类似c语言中的switch,他会判断表达式进入第一个符合的模式,并且可以返回任意类型的值。

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

fn main() {}

match后面跟表达式,第一个分支用Coin::Penny作为模式,紧跟=>运算符用于将模式和代码区分开。这部分返回了值1。不同分支使用逗号隔开。模式匹配成功就执行代码,并退出match,模式匹配失败就继续向下匹配。 可以使用花括号包裹模式后面指向的代码,这样可以写多行代码

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => {
            println!("Lucky penny!");
            1
        }
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}


绑定值的模式

可以在模式中绑定值,这样就可以在逻辑代码中使用绑定的值。

#[derive(Debug)]
enum UsState {
    Alabama,
    Alaska,
    // --snip--
}

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(UsState),
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter(state) => {
            println!("State quarter from {:?}!", state);
            25
        }
    }
}

fn main() {
    value_in_cents(Coin::Quarter(UsState::Alaska));
}

匹配Option< T >

我们可以通过match来处理Option< T >

fn main() {
    fn plus_one(x: Option<i32>) -> Option<i32> {
        match x {
            None => None,
            Some(i) => Some(i + 1),
        }
    }

    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);
}

当调用plus_one(five)时,x为Some(5),匹配到Some(i),返回Some(6),这里的i绑定了x中5的值。 第二次调用plus_one(None),传递了None,匹配到None,返回了None。

(其实我觉得这个例子不好,match返回的值可以不是Option,这样才能体现对空值的处理。)

match匹配必须穷举所有可能

//错误代码
fn main() {
    fn plus_one(x: Option<i32>) -> Option<i32> {
        match x {
            Some(i) => Some(i + 1),
        }
    }

    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);
}

上面的错误代码忘记了处理None的情形,这是一个rust能轻松捕获的问题。这样的设计也是合理的、必要的。

_通配符

因为你必须处理所有的可能,所以你在匹配一个类型为u8的数据时需要匹配0到255。假设我们只关心1、3、5、7,那么我们可以通过_运算符来代表剩余的值。

fn main() {
    let some_u8_value = 0u8;
    match some_u8_value {
        1 => println!("one"),
        3 => println!("three"),
        5 => println!("five"),
        7 => println!("seven"),
        _ => (),
    }
}

简单控制流if let

if let 能让我们通过不那么繁琐的语法结合使用if与let,并处理那些只用关心某一种匹配而忽略其他匹配的情况。(就是区别与match需要穷举所有可能)

fn main() {
    let some_u8_value = Some(0u8);
    if let Some(3) = some_u8_value {
        println!("three");
    }
}

可以在if let后搭配使用else:

#[derive(Debug)]
enum UsState {
    Alabama,
    Alaska,
    // --snip--
}

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(UsState),
}

fn main() {
    let coin = Coin::Penny;
    let mut count = 0;
    if let Coin::Quarter(state) = coin {
        println!("State quarter from {:?}!", state);
    } else {
        count += 1;
    }
}

下一章:Rust 错误处理

Rust的错误分两种: 可恢复错误不可恢复错误 rust提供了可恢复错误的类型Result< T,E >,与不可恢复错误时终止运行的panic!宏。 不可恢复错误与panic! 程序会在panic!宏执行时打 ...