Rust入门失败之Enum&Patterns

Enum

  • 成员可以是任意数据类型(with data or without data):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    enum Pet {  // without data
    Orca, // Starting at 0 by default
    Giraffe
    }

    #[derive(Copy, Clone, Debug, PartialEq)]
    enum RoughTime { // with data
    InThePast(TimeUnit, u32),
    JustNow,
    InTheFuture(TimeUnit, u32)
    }
  • 可以范型, 最常见的就是Option<T>Result<T, E>:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    enum Option<T> {
    None,
    Some(T)
    }

    enum Result<T, E> {
    Ok(T),
    Err(E)
    }
  • 所有成员大小必须固定, 可以在编译期确定. 如果动态大小, 需要用Box<T>等智能指针封装. 上面的Option<T>中, 如果T是Box等智能指针类型, 那么整个Option<T>大小可能就是一个机器字(machine word), 0代表None, 非0代表Boxed指针值, 没有tag字段(下面讲到).

内存布局

  • Rust为了将来的优化考虑, 没有强制规定enum的内存布局, 但同一类型的枚举值大小一致.

  • 通常都为每个枚举值分配了适合的small intger类型存放其枚举tag

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    enum Ordering {
    Less,
    Equal,
    Greater
    }

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

    use std::mem::size_of;
    assert_eq!(size_of::<Ordering>(), 1);
    assert_eq!(size_of::<HttpStatus>(), 2); // 404 doesn't fit in a u8
  • 对于包含数据的enum, 内存布局有点类似C/C++的union类型, 除了tag字段外会用选择合适的字节来存放可以容纳所有数据大小的内存布局. 例如:

    1
    2
    3
    4
    5
    6
    7
    8
    enum Json {
    Null,
    Boolean(bool),
    Number(f64),
    String(String),
    Array(Vec<Json>),
    Object(Box<HashMap<String, Json>>)
    }

    它的内存布局通常可能如下(上面短短的8行代码, 如果用C/C++来实现, 就非常复杂了):

    Enum Memory Model

Patterns 模式匹配

模式匹配通常会和enum类型一起使用, 所以放到一起讲.

  • 匹配是从左到右检查Pattern是否匹配表达式. 表达式生成值, Pattern消费这些值(Expressions produce values; patterns consume values)

  • refref mut用来避免move或者Copy匹配到的变量, 从而用于match的变量在后续可以继续使用. 例如:

    1
    2
    3
    4
    5
    6
    match account {
    Account {name, language, .. } => {
    ui.greet(&name, &language);
    ui.show_settings(&account); // error: use of moved value `account`
    }
    }

    这里匹配到的数据已经被move到namelanguage中, 原变量account已经为uninitialized, 不能再使用, 用ref将匹配到的变量转为引用.

    1
    2
    3
    4
    5
    6
    match account {
    Account { ref name, ref language, .. } => {
    ui.greet(name, language);
    ui.show_settings(&account); // ok
    }
    }
  • &用来匹配引用类型的pattern, 而非将匹配到的变量转换为引用.

    1
    2
    3
    match sphere.center() {  // sphere.center() return &Point3d {...}
    &Point3d { x, y, z } => ...
    }

    道理其实一样, 表达式和Pattern是两个本质上相反(natural opposites)的过程.把表达式看成是构造一个值的过程, 而Pattern是反解一个值的过程, 加上&也是一样.

    Reference Pattern

  • Pattern Guard: pattern if condition⇒ 只有条件满足, pattern才算匹配

  • var @ pattern: 将匹配的整个pattern move或Copy到变量var中, 例如某些场景下可以避免重复构造变量.

  • 除了match var {}语句可以使用pattern外, 只有以下四个场景可以使用pattern:

    • let pattern = var;将变量var按匹配模式unpack

    • fn func(parm: pattern) → xxx{}按匹配模式unpack函数实参

    • for pattern in xxxx {}for循环中按匹配模式unpack实际迭代的成员

    • let var = func(|pattern| {closure body})在闭包中按模式unpack闭包的参数, 例如有时期望参数是引用而非拷贝.

      let sum = numbers.fold(0, |a, &num| a+ num);

    这四个地方和match的模式匹配最根本的区别是:

    • matchrefutable pattern, 允许出现模式不匹配的情况
    • 而上述四种场景Rust不允许出现不匹配(irrefutable)的情况, 属于严格的不可驳斥匹配模式.

All images are copyrighted by original authors Jim Blandy & Jason Orendorff who wrote in the book Programming Rust.