一、Rust 枚举

(一)枚举的定义

枚举允许自定义类型,取值范围只能取自预定义的命名常量的集合。

枚举就像是一个集合,其中的每个成员都是这个集合的一个特定值。

枚举适用于一个值有多种可能的情况。但必须使用模式匹配来安全地访问数据。

例如,一个Color 的类型,取值范围为Red、Orange、Yellow 等等。

enum Color { 
    Red, 
    Orange, 
    Yellow,
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

在内存中,枚举值被存储为整数。可以为枚举值指定数值,否则,Rust 会从0 开始自动分配值。

enum HttpStatus { 
    Ok = 200, 
    NotModified = 304, 
    NotFound = 404, 
    ... 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

默认情况下,Rust 使用能容纳所有值的最小的内建整数类型来存储枚举。大多数情况下都是一个单独的字节,

将枚举转换为整数是允许的:

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

然而,反过来把整数转换为枚举是不允许的。

枚举也和结构体一样可以拥有方法:

impl TimeUnit { 
    /// 返回该时间单位的复数名词。 
    fn plural(self) -> &'static str { 
        match self { 
            TimeUnit::Seconds => "seconds", 
            TimeUnit::Minutes => "minutes", 
            TimeUnit::Hours => "hours", 
            TimeUnit::Days => "days", 
            TimeUnit::Months => "months", 
            TimeUnit::Years => "years", 
        } 
    } 
    /// 返回该时间单位的单数名词。 
    fn singular(self) -> &'static str { 
        self.plural().trim_end_matches('s') 
    } 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

(二)枚举的赋值与取值

枚举的赋值非常直观,比如let red = Color::Red;就是将枚举值Red赋给变量red。

取值可以通过直接打印,如println!("{:?}", red);,也可以通过match语句来根据不同的枚举值执行不同的操作。

match some_enum_value { 
    EnumVariant1 => // do something for EnumVariant1, 
    EnumVariant2 => // do something for EnumVariant2,
    _ => // handle other cases if needed. 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

(三)带有关联数据的枚举

带有关联数据的枚举可以让我们在枚举成员中关联特定的数据类型。

比如Message枚举类型,其中Move成员与一个包含x和y坐标的结构体关联,Write成员与一个字符串关联,ChangeColor成员与三个整数关联。通过模式匹配,我们可以方便地访问这些关联数据。例如:

enum Message { 
    Quit, 
    Move { 
        x: i32, 
        y: i32
    }, 
    Write(String), 
    ChangeColor(u8, u8, u8), 
} 
fn process_message(message: Message) { 
    match message { 
        Message::Quit => println!("Quit"), 
        Message::Move { x, y } => println!("Move to ({}, {})", x, y),
        Message::Write(text) => println!("Write: {}", text),
        Message::ChangeColor(r, g, b) => println!("Change color to RGB({}, {}, {})", r, g, b), 
   } 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

Rust 有三种枚举值,没有数据的对应类单元结构体。元组的对应类元组结构体。结构体的对应有花括

号和命名字段的结构体。一个枚举可以同时有这三种值:

enum RelationshipStatus { 
    Single, 
    InARelationship, 
    ItsComplicated(Option<String>), 
    ItsExtremelyComplicated { 
        car: DifferentialEquation, 
        cdr: EarlyModernistPoem, 
    }, 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

(四)使用 Option 枚举处理空值

Option枚举是 Rust 中用于处理可能为空的值的强大工具。在除法函数中,如果除数为 0,则返回None,否则返回Some并包含除法运算的结果。例如:

fn divide(dividend: f64, divisor: f64) -> Option<f64> { 
    if divisor == 0.0 { 
        None 
    } else { 
        Some(dividend / divisor) 
    } 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

(五)泛型枚举

枚举可以使用泛型,最典型的就是经常在标准库使用的Option和Result。泛型枚举的语法和泛型结构体的语法完全一致。

enum Option<T> { 
    None, 
    Some(T), 
} 
enum Result<T, E> { 
    Ok(T), 
    Err(E), 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

以下用几行代码定义了一个可以存储任意数量的T 类型值的BinaryTree 类型:

// 一个`T`类型的有序集合 
enum BinaryTree<T> { 
    Empty, 
    NonEmpty(Box<TreeNode<T>>), 
} 
// 二叉树的一部分 
struct TreeNode<T> { 
    element: T, 
    left: BinaryTree<T>, 
    right: BinaryTree<T>, 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

二、Rust 模式

(一)模式的定义与使用场景

模式在 Rust 中是一种特殊的语法,主要用来匹配类型中的结构,无论类型是简单还是复杂。结合使用模式和 match 表达式以及其他结构可以提供更多对程序控制流的支配权。模式在 Rust 中有广泛的应用场景,包括 let、if let、while let、for循环、函数参数和 match等场景。

如果想要访问枚举里面的数据,是不允许直接访问的,比如 let a = Some(1);是一个Option的枚举,但是不能直接使用a.0来访问里面的数据1,因为Option类型有可能是None,所以就需要使用模式匹配。

let a = Some(1); 
match a { 
    Some(b) => println!("{}}", b), 
    _ => println!("None")
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

(二)模式的种类

1. 字面量、变量、通配符模式:

字面量是诸如整数、浮点数、字符、字符串、布尔值等。它们可以直接作为模式,例如:

let a = 1; 
match a { 
    1 => println!("a is 1"), 
    2 => println!("a is 2"), 
    n => println!("a is other"), 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

如果需要一个匹配所有值的模式,但又不关心匹配到的值,你可以使用单个下划线_ 作为模式,也就是通配模式:

let a = 2; 
match a { 
    1..=5 => println!("a is bigger than 1 and smaller than 5"), 
    _ => println!("other"), 
} 
let b = 'd'; 
match b { 
    'a'..='z' => println!("a is bigger than a and smaller than z"), 
    _ => println!("other"), 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

2. 结构体或元组模式:

常用于复杂数据类型,匹配数据的多个部分

struct Point(f32, f32); 
let a = Point(1.2, 3.4); 
match a { 
    Point(1.2, y) => {}, 
    Point(x, y) => {}, 
} 

struct Point3d { 
    x: f32, 
    y: f32, 
    z: f32, 
}
let b = Point3d { 
    x: 1, 
    y: 2, 
    z: 3, 
}; 
match b { 
    Point3d { x: 1, y: 2, .. } => {}, 
    _ => {} 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

3. 数组或切片模式:

数组模式匹配数组,通常用来过滤出某些特殊值:

let hsl = [12, 211, 3] 
match hsl { 
    [_, _, 0] => [0, 0, 0], 
    [_, _, 255] => [255, 255, 255], 
    ..., 
    _ => {} 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

切片模式与数组类似,但不同的是,切片的长度可以变化,切片模式中的.. 匹配任意数量的元素:

let names = &["a", "b", "c"]; 
match names { 
    [] => { println!("Hello, nobody.") }, 
    [a] => { println!("Hello, {}.", a) }, 
    [a, b] => { println!("Hello, {} and {}.", a, b) }, 
    [a, .., b] => { println!("Hello, everyone from {} to {}.", a, b) } 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

4. 引用模式:

Rust 模式支持两种和引用有关的特性。ref 模式会借用被匹配的值,& 模式匹配引用:

struct Account { 
    name: String, 
    language: String, 
    gender: String, 
} 
let account = Account { 
    name: "tom".to_string(), 
    language: "Chinese".to_string(), 
    gender: "man".to_string(), 
}; 
// 如果不加 ref ,编译报错,提示会出现所有权转移 
match account { 
    Account { ref name, ref language, .. } => { 
        greet(name, language); 
        show_settings(&account); // ok 
    }, 
    _ => {}, 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

& 模式,一个以&开始的模式只能匹配引用:

struct Point3d { 
    x: f32, 
    y: f32, 
    z: f32, 
} 
let b = Point3d { 
    x: 1, 
    y: 2, 
    z: 3, 
}; 

let a = &b; 

match a { 
    &Point3d { x, y, z } => {}, 
    _ => {} 
} 

let c = Some("c"); 
match c { 
    Some(&d) => {}, 
    _ => {} 
} 


// ref与&一起使用 
struct Engine { 
    width: f32, 
    height: f32, 
    type: String, 
} 
struct Wheel { 
    circle: f32, 
    color: String, 
} 
struct Car { 
    engine: Engine, 
    wheel: Wheel, 
} 
let car = Car { 
    engine: Engine { 
        width: 100, 
        height: 200, 
        type: "f4".to_string(), 
    }, 
    wheel: Wheel { 
        circle: 50, 
        color: "black".to_string(), 
    }, 
} 
let borrow_car = Some(&car); 
match borrow_car { 
    // Some(&Car { engine, .. }) error: can't move out of borrow 
    Some(&Car { ref engine, .. }) => {}, 
    ... 
    None => {} 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.

5. 匹配守卫:

有时一个匹配分支还需要附加条件才能满足,可以在分支模式后添加条件:

let x = 4; 
let y = false; 
match x { 
    4 if y => println!("yes"), 
    n => println!("{}", n), 
    _ => println!("no"), 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

6. 匹配多个模式:

使用 | 语法。例如:

let x = 4; 
let y = false; 
match x { 
    4 | 5 => println!("yes"), 
    _ => println!("no"),
} 

let next_char = 'a'; 
// Rust(目前)不允许在模式中使用尾开区间,例如0..100。 
match next_char { 
    '0'..='9' => self.read_number(), 
    'a'..='z' | 'A'..='Z' => self.read_word(), 
    ' ' | '\t' | '\n' => self.skip_whitespace(), 
    _ => self.handle_punctuation(), 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

7. 绑定和@模式:

x @ pattern 用给定的pattern 来匹配,匹配成功时它会创建单个变量x ,并把整个值移动或拷贝进去,而不是为匹配值的每一部分创建一个变量。

struct Point3d { 
    x: f32, 
    y: f32, 
    z: f32, 
} 
let a = Point3d { 
    x: 1, 
    y: 2, 
    z: 3,
}; 
match a { 
    Point3d { x, y, z } => { 
        some_fn(&Point3d { x, y, z}); 
    }, 
    _ => {} 
} 
// 绑定和@模式 
match a { 
    b @ Point3d { x, y, z } => { 
        some_fn(&b); 
    }, 
    _ => {} 
} 

// 与范围一起 
let x = 1u32; 
match x { 
    e @ 1..=5 | e @ 10..=15 => println!("get:{}", e), 
    _ => (), 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.