DAILY DOCDAILY DOC
Rust
Node
Notes
Ubuntu
Leetcode
  • it-tools
  • excalidraw
  • linux-command
Rust
Node
Notes
Ubuntu
Leetcode
  • it-tools
  • excalidraw
  • linux-command
  • rust

    • Rust
    • add
    • 属性(attributes)
    • cargo issue
    • cli
    • build.rs
    • Enums
    • eventEmitter(rust)
    • 格式化输出 std::fmt
    • rust iterator
    • rust 学习计划
    • 生命周期(lifetime)
    • Linked List
    • log
    • macros
    • mem::size_of
    • niche optimization
    • Rust 所有权
    • 模式匹配(pattern matching)
    • module system
    • result & option
    • .rust-analyzer.json
    • rust startup
    • rust-test
    • 可见性(visibility)
    • cargo
    • toml

Enums

  • Option
  • Result

对比不同语言中 enum

从写法上对比一下

typescript

enum Direction {
    North,
    East,
    South,
    West
}
// Using the enum
let dir: Direction = Direction.North;

c++

enum Direction {
    North,
    East,
    South,
    West
};

int main() {
    Direction dir = North;
}

rust

enum Direction {
    North,
    East,
    South,
    West,
}
// Using the enum
let dir: Direction = Direction::North;

语法 & 规范

  • 语法
    • field-less enum
    • unit-only enum
  • 规范
    • Pascal Case
    • snake_case

fn main() {
    #[derive(Debug)]
    enum Pets {
        Cat,                           // unit enum
        Fish(String),                  // tuple
        Dog { name: String, age: u8 }, // struct
        Bird,
    }

    let cat = Pets::Cat;
    printPet(cat);
    // .. dog , bird
    //

    let fish = Pets::Fish("gold-fish".to_string());
    printPet(fish);

    fn printPet(name: Pets) -> () {
        println!("Pet is {:?}", name);
    }

    // methods
    impl Pets {
        // methods
        fn log(&self) {
            println!("pet is {:?}", self);
        }
    }
    let dog = Pets::Dog {
        name: "lucky".to_owned(),
        age: 1,
    };
    dog.log();

    // associated fn
    impl Pets {
        fn print(kind: &Pets) {
            println!("kind is {:?}", kind);
        }
    }
    Pets::print(&dog);
}


Discriminants 判别式

enumerations

fn main() {
    enum Fieldless {
        Tuple(),
        Struct {},
        Unit,
    }

    assert_eq!(0, Fieldless::Tuple() as isize);

    enum UnitOnlyEnum {
        Foo = 0,
        Bar = 1,
        Baz = 2,
    }
    enum UnitOnlyEnum2 {
        Foo,
        Bar,
        Baz,
    }
    assert_eq!(UnitOnlyEnum::Foo as isize, 0);
    assert_eq!(UnitOnlyEnum2::Foo as isize, 0);
}

用法

  • match vs if let
  • match 必须能匹配所有条件

fn main() {
    #[derive(PartialEq)]
    enum Pets {
        Cat,
        Dog,
    }
    let a = Pets::Cat;
    let b = Pets::Dog;
    if a == b {
        println!("same pet");
    } else {
        println!("different pet");
    }
}

fn main() {
    enum Pets {
        Cat,
        Dog,
    }
    let cat = Pets::Cat;
    print(cat);

    fn print(pet: Pets) {
        match pet {
            Pets::Cat => {
                println!("cat");
            }
            Pets::Dog => {
                println!("dog");
            }
        }
    }
}

Option

null a billion-dollar mistake

In his 2009 presentation “Null References: The Billion Dollar Mistake,” Tony Hoare, the inventor of null, has this to say:

  • Some<T>
  • None

let a = Some(1);
match a {
    Some(val) => {
        println!("val is {val}");
    }
    None => {}
}
use std::collections::HashMap;
let map: HashMap<&str, usize> = HashMap::new();
let a = map.get("a");
match a {
    Some(val) => {}
    None => {}
}

let vec = vec![1, 2, 3];
let a = vec.iter().last();
match a {
    Some(val) => {}
    None => {}
}

Result

  • Ok<T>
  • Err<E>

let a = Ok(1);
match a {
    Ok(_) => {},
    Err(_) => {},
}

main 函数也能返回 result 类型

fn main() -> Result<(), ()> {
    let a: Result<i32, ()> = Ok(1);
    match a {
        Ok(val) => Ok(()),
        Err(_) => Err(()),
    }
}
let a: Result<usize, ParseIntError> = "24".parse();
match a {
    Ok(_) => {}
    Err(_) => {}
}

文件系统 API

let result = File::open("foo.txt");
match result {
    Ok(_) => {}
    Err(_) => {}
}

网络模块

let result = TcpStream::connect("127.0.0.1:8080");
match result {
    Ok(stream) => println!("Connected to the server"),
    Err(e) => println!("Failed to connect: {}", e),
}

Option & Result

取值

let a = Some(1);
let b: Result<i32, ()> = Ok(1);

let c = a.unwrap();
let d = a.expect("error");

let e = b.unwrap();
let f = b.expect("");

Option & Result 转换

  • ok_or*
  • ok() err()
  • map

Option 转 Result

let opt: Option<i32> = Some(42);
let result: Result<i32, &str> = opt.ok_or("error");
assert_eq!(result, Ok(42));

let none: Option<i32> = None;
let result: Result<i32, &str> = none.ok_or("error");
assert_eq!(result, Err("error"));

Result 转 Option

let res: Result<i32, &str> = Ok(42);
let opt: Option<i32> = res.ok();
assert_eq!(opt, Some(42));

let res: Result<i32, &str> = Err("error");
let opt: Option<i32> = res.ok();
assert_eq!(opt, None);


常用操作

let opt: Option<i32> = Some(1);
let a1 = opt.map(|num| num > 0);
assert!(a1.unwrap());
let b1 = opt.and_then(|val| Some(val + 1));
assert_eq!(b1, Some(2));
let c1 = opt.or_else(|| None);
assert_eq!(c1, Some(1));

let ret: Result<i32, &str> = Ok(1);
let a2 = ret.map(|val| val > 0);
assert!(a2.unwrap());
let b2 = ret.and_then(|val| Ok((val + 1)));
assert_eq!(b2, Ok(2));
let c2 = ret.or_else(|str| Err(3));
assert_eq!(c2, Ok(1));

println!("done");

enum mem

  • largest variant
  • discriminant
use std::mem::size_of;

#[derive(Debug)]
enum MyEnum {
    A,
    B(i32),
    C { x: f64, y: f64 },
}
fn main() {
    println!("Size of MyEnum: {}", size_of::<MyEnum>());
}


课后习题

分析 enum 所占内存大小,并通过print打印出来

enum MyEnum {
    A(u8, u8),
    B,
    C {},
}

enum EnumA {
    A = 255,
}

enum EnumA {
    A = 255,
    B,
}

Enum 内存分析

Enum Sizes

In Rust, an enum's memory layout is determined by the size of its largest variant and a discriminant, which indicates which variant is currently being used.

Let's analyze the MyEnum enum:

enum MyEnum {
    A(u8, u8),
    B,
    C {},
}

enum EnumA {
    A = 255,
}

enum EnumA {
    A = 255,
    B,
}

Variant Sizes:

  1. A(u8, u8): This variant contains two u8 values. Each u8 is 1 byte, so the size of this variant is 2 bytes.
  2. B: This variant does not contain any data, so its size is 0 bytes.
  3. C {}: This variant also does not contain any data, so its size is 0 bytes.

Discriminant:

Rust uses a discriminant to keep track of which variant is currently active. The size of the discriminant depends on the number of variants:

  • Since there are 3 variants, Rust will use at least a u8 (1 byte) for the discriminant, as it can represent up to 256 different values.

Alignment:

Rust aligns data types to specific byte boundaries for performance reasons. For u8, the alignment is 1 byte.

Total Size:

To determine the total size of the enum, we take the size of the largest variant and add the size of the discriminant:

  • The largest variant is A(u8, u8), which is 2 bytes.
  • The discriminant is 1 byte.

However, due to alignment requirements, the total size will be aligned to the nearest multiple of the alignment factor of the largest variant.

So, the total size is 3 bytes (2 bytes for the largest variant + 1 byte for the discriminant).

Let's confirm this with a Rust program:

use std::mem::size_of;

enum MyEnum {
    A(u8, u8),
    B,
    C {},
}

fn main() {
    println!("Size of MyEnum: {}", size_of::<MyEnum>());
}

Running the Program:

When you run the above program, you should get the output:

Size of MyEnum: 3

This confirms that the total size of the MyEnum enum is 3 bytes.

Summary:

  • The size of the largest variant is 2 bytes (for A(u8, u8)).
  • The discriminant adds 1 byte.
  • The total size of the enum, considering alignment, is 3 bytes.

判别式 (Discriminant) 的概念

Rust

在 Rust 中,枚举类型(enum)的每个变体都有一个对应的判别式(discriminant)值,用于区分不同的变体。判别式通常是一个整数,可以通过 std::mem::discriminant 函数来获取它。

判别式是一个隐式整数值,它为每个枚举变体分配一个唯一的标识符。默认情况下,Rust 为枚举变体自动分配连续的整数值,从 0 开始。

例如,考虑以下枚举类型:

enum MyEnum {
    A,
    B,
    C,
}

在这个枚举中:

  • A 的判别式值是 0
  • B 的判别式值是 1
  • C 的判别式值是 2

自定义判别式值

可以为枚举变体显式指定判别式值:

enum MyEnum {
    A = 1,
    B = 3,
    C = 5,
}

在这个枚举中:

  • A 的判别式值是 1
  • B 的判别式值是 3
  • C 的判别式值是 5

使用 std::mem::discriminant 获取判别式值

Rust 提供了 std::mem::discriminant 函数,用于在运行时获取枚举值的判别式。此函数返回一个 Discriminant 类型的值,这个值可以用于比较但不能直接访问其内部整数值。

示例代码:

use std::mem;

#[derive(Debug)]
enum MyEnum {
    A,
    B,
    C,
}

fn main() {
    let a = MyEnum::A;
    let b = MyEnum::B;
    let c = MyEnum::C;

    println!("Discriminant of A: {:?}", mem::discriminant(&a));
    println!("Discriminant of B: {:?}", mem::discriminant(&b));
    println!("Discriminant of C: {:?}", mem::discriminant(&c));

    // Comparing discriminants
    assert!(mem::discriminant(&a) != mem::discriminant(&b));
    assert!(mem::discriminant(&b) != mem::discriminant(&c));
}

std::mem::discriminant 的返回类型

std::mem::discriminant 返回一个 Discriminant<T> 类型的值,其中 T 是枚举类型。Discriminant<T> 是一个不透明类型,只能用于相等性比较,而不能访问其内部的具体值。

结合判别式的高级用法

可以使用判别式来实现模式匹配的高级用法,例如在不匹配具体值的情况下判断枚举值的类型:

use std::mem;

enum MyEnum {
    A,
    B,
    C,
}

fn print_discriminant(value: &MyEnum) {
    match mem::discriminant(value) {
        d if d == mem::discriminant(&MyEnum::A) => println!("Variant is A"),
        d if d == mem::discriminant(&MyEnum::B) => println!("Variant is B"),
        d if d == mem::discriminant(&MyEnum::C) => println!("Variant is C"),
        _ => unreachable!(),
    }
}

fn main() {
    let a = MyEnum::A;
    let b = MyEnum::B;
    let c = MyEnum::C;

    print_discriminant(&a);
    print_discriminant(&b);
    print_discriminant(&c);
}

总结

  • 枚举变体的判别式用于区分不同的枚举值,默认从 0 开始递增。
  • 可以显式为枚举变体指定判别式值。
  • 使用 std::mem::discriminant 函数可以在运行时获取枚举值的判别式,用于类型判断和比较。

通过这些机制,Rust 提供了强大的枚举类型支持和运行时类型识别功能,使得处理复杂的数据结构更加灵活和高效。

Last Updated:
Contributors: rosendo
Prev
build.rs
Next
eventEmitter(rust)