Rust 枚举与模式

枚举

例子 1

pub enum Car {
    Benz,
    Ferrari,
}

fn get_car(car: Car) -> Engine<String> {
    match car {
        Car::Benz => Engine::new("V8".to_string(), 8),
        Car::Ferrari => Engine::FERRARI,
    }
}

例子 2

给枚举设置参数

enum HttpStatus {
    Ok = 200,
    NotModified = 304,
    NotFound = 404
}

例子 3

可以直接使用 as 强制转换为整数

assert_eq!(HttpStatus::Ok as i32, 200);

例子 4

结构体一样,编译器能为你实现 == 运算符等特性,但你必须明确提出要求

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum TimeUnit {
    Seconds, Minutes, Hours, Days, Months, Years,
}

带数据的枚举

/// 刻意四舍五入后的时间戳,所以程序会显示“6个月前”
/// 而非“2016年2月9日上午9点49分”
#[derive(Copy, Clone, Debug, PartialEq)]
enum RoughTime {
    InThePast(TimeUnit, u32),
    JustNow,
    InTheFuture(TimeUnit, u32),
}

let four_score_and_seven_years_ago =
    RoughTime::InThePast(TimeUnit::Years, 4 * 20 + 7);

let three_hours_from_now =
    RoughTime::InTheFuture(TimeUnit::Hours, 3);

//包含字段名称的结构体

enum Shape {
    Sphere { center: Point3d, radius: f32 },
    Cuboid { corner1: Point3d, corner2: Point3d },
}

let unit_sphere = Shape::Sphere {
    center: ORIGIN,
    radius: 1.0,
};

内存中的枚举会占用 8 个字符

用枚举表示富数据结构

use std::collections::HashMap;

enum Json {
    Null,
    Boolean(bool),
    Number(f64),
    String(String),
    Array(Vec<Json>),
    Object(Box<HashMap<String, Json>>),
}

泛型枚举

枚举可以是泛型的。Rust 标准库中的两个例子是该语言中最常用的数据类型:

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

enum Result<T, E> {
    Ok(T),
    Err(E),
}

模式

Rust 不允许你通过编写 rough_time.0 和 rough_time.1 来直接访问它们,因为毕竟 rough_time 也可能是没有字段的,比如 RoughTime::JustNow。

你需要一个 match 表达式:

 1  fn rough_time_to_english(rt: RoughTime) -> String {
 2      match rt {
 3          RoughTime::InThePast(units, count) =>
 4              format!("{} {} ago", count, units.plural()),
 5          RoughTime::JustNow =>
 6              format!("just now"),
 7          RoughTime::InTheFuture(units, count) =>
 8              format!("{} {} from now", count, units.plural()),
 9      }
10  }

模式中的字面量、变量和通配符

其他字面量也可以用作模式.

let calendar = match settings.get_string("calendar") {
    "gregorian" => Calendar::Gregorian,
    "chinese" => Calendar::Chinese,
    "ethiopian" => Calendar::Ethiopian,
    other => return parse_error("calendar", other),
};

通配符模式

let caption = match photo.tagged_pet() {
    Pet::Tyrannosaur => "RRRAAAAAHHHHHH",
    Pet::Samoyed => "*dog thoughts*",
    _ => "I'm cute, love me", // 一般性捕获,对任意Pet都生效
};

元组型模式与结构体型模式

元组型模式匹配元组。每当你想要在单次 match 中获取多条数据时,元组型模式都非常有用.

fn describe_point(x: i32, y: i32) -> &'static str {
    use std::cmp::Ordering::*;
    match (x.cmp(&0), y.cmp(&0)) {
        (Equal, Equal) => "at the origin",
        (_, Equal) => "on the x axis",
        (Equal, _) => "on the y axis",
        (Greater, Greater) => "in the first quadrant",
        (Less, Greater) => "in the second quadrant",
        _ => "somewhere else",
    }
}

结构体型模式使用花括号,就像结构体表达式一样。结构体型模式包含每个字段的子模式.

match balloon.location {
    Point { x: 0, y: height } =>
        println!("straight up {} meters", height),
    Point { x: x, y: y } =>
        println!("at ({}m, {}m)", x, y),
}

image00854.jpeg

数组型模式与切片型模式

fn hsl_to_rgb(hsl: [u8; 3]) -> [u8; 3] {
    match hsl {
        [_, _, 0] => [0, 0, 0],
        [_, _, 255] => [255, 255, 255],
        ...
    }
}

引用型模式

match account {
    Account { name, language, .. } => {
        ui.greet(&name, &language);
        ui.show_settings(&account);  // 错误:借用已移动的值`account`
    }
}

匹配守卫

fn check_move(current_hex: Hex, click: Point) -> game::Result<Hex> {
    match point_to_hex(click) {
        None =>
            Err("That's not a game space."),
        Some(current_hex) =>  // 如果用户单击current_hex,就会尝试匹配
                              //(其实它不起作用:请参见下面的解释)
            Err("You are already there! You must click somewhere else."),
        Some(other_hex) =>
            Ok(other_hex)
    }
}

匹配多种可能性

let at_end = match chars.peek() {
    Some(&'\r' | &'\n') | None => true,
    _ => false,
};

使用 @模式绑定

match self.get_selection() {
    Shape::Rect(top_left, bottom_right) => {
        optimized_paint(&Shape::Rect(top_left, bottom_right))
    }
    other_shape => {
        paint_outline(other_shape.get_outline())
    }
}

模式能用在哪里

尽管模式在 match 表达式中作用最为突出,但它们也可以出现在其他一些地方,通常用于代替标识符。但无论出现在哪里,其含义都是一样的:Rust 不是要将值存储到单个变量中,而是使用模式匹配来拆分值。

// 把结构体解包成3个局部变量……
let Track { album, track_number, title, .. } = song;

// ……解包某个作为函数参数传入的元组
fn distance_to((x, y): (f64, f64)) -> f64 { ... }

// ……迭代某个HashMap上的键和值
for (id, document) in &cache_map {
    println!("Document #{}: {}", id, document.title);
}

// ……自动对闭包参数解引用(当其他代码给你传入引用,
// 而你更想要一个副本时会很有用)
let sum = numbers.fold(0, |a, &num| a + num);

附件

模式对照表

模式类型 例子 注意事项
字面量 "name" 匹配一个确切的值;也允许匹配常量名称
范围 0 ..= 100 匹配范围内的任何值,包括可能给定的结束值
通配符 _ 匹配任何值并忽略它
变量 name 类似于 _,但会把值移动或复制到新的局部变量中
引用变量 ref field 借用对匹配值的引用,而不是移动或复制它
与子模式绑定 val @ 0 ..= 99 使用 @ 左边的变量名,匹配其右边的模式
枚举型模式 Some(value)
元组型模式 (key, value)
数组型模式 [a, b, c, d, e, f, g]
切片型模式 [first, second]
结构体型模式 Color(r, g, b)
引用 &value
或多个模式 `'a' 'A'`
守卫表达式 x if x * x <= r2