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 判别式
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
vsif 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 mistakeIn 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:
- A(u8, u8): This variant contains two
u8
values. Eachu8
is 1 byte, so the size of this variant is2 bytes
. - B: This variant does not contain any data, so its size is
0 bytes
. - 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 is2 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
(forA(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
的判别式值是 0B
的判别式值是 1C
的判别式值是 2
自定义判别式值
可以为枚举变体显式指定判别式值:
enum MyEnum {
A = 1,
B = 3,
C = 5,
}
在这个枚举中:
A
的判别式值是 1B
的判别式值是 3C
的判别式值是 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 提供了强大的枚举类型支持和运行时类型识别功能,使得处理复杂的数据结构更加灵活和高效。