- 定义和创建实例
- ![allow(unused)]
- [derive(Debug)]
- [derive(Debug)]
- ![allow(unused)]
- [derive(Clone, Copy)]
- [derive(Clone, Copy)]
- ![allow(dead_code)]
- [derive(Debug)] enum Food { Apple, Carrot, Potato }
- [derive(Debug)] struct Peeled(Food);
- [derive(Debug)] struct Chopped(Food);
- [derive(Debug)] struct Cooked(Food);
- ![allow(dead_code)]
- [derive(Debug)] enum Food { CordonBleu, Steak, Sushi }
- [derive(Debug)] enum Day { Monday, Tuesday, Wednesday }
参考:https://kaisery.github.io/trpl-zh-cn/ch06-00-enums.html
枚举 (enumerations 表示列举、一一列出来),也被称作 enums 。枚举允许你通过列举可能的 成员 (variants ) 来定义一个类型。
首先,我们会定义并使用一个枚举来展示它是如何连同数据一起编码信息的。接下来,我们会探索一个特别有用的枚举,叫做 Option
,它代表一个值要么是某个值要么什么都不是。然后会讲到在 match
表达式中用模式匹配,针对不同的枚举值编写相应要执行的代码。最后会介绍 if let
,另一个简洁方便处理代码中枚举的结构。
枚举是一个很多语言都有的功能,不过不同语言中其功能各不相同。Rust 的枚举与 F#、OCaml 和 Haskell 这样的函数式编程语言中的 代数数据类型 (algebraic data types )最为相似。
定义和创建实例
枚举体时一种类型,但其成员并不是类型。
- 枚举体的成员可以是结构体 ``` enum 枚举体名称 { 成员结构体1, 成员结构体2, … }
借助结构体,成员最终可以支持诸多类型:原生类型,以及元组、结构体、枚举体等自定义类型。<br />创建实例的方式:`枚举体名称::成员名称`。具体的创建过程与成员的 "结构体形式" 有关。<br />比如有以下 ip 4/6 地址的枚举体(标准库的伪代码):
struct Ipv4Addr { // —snip— }
struct Ipv6Addr { // —snip— }
enum IpAddr { V4(Ipv4Addr), V6(Ipv6Addr), }
首先要清楚每个成员 "是什么形式的结构体" ,V4 / V6 成员具有元组结构体的形式,因此用 `IpAddr::V4()`、`IpAddr::V6()` 来进行初始化;其次,括号内部就是结构体实例(见 "`{}` 型" 、"`()` 型"),因此最终实例化该枚举体的代码(见 [Enum std::net::IpAddr](a4df0711e0f14328db78825e64787054)):
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; // 即便是来自标准库的类型,也不是默认导入 let localhost_v4 = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); let localhost_v6 = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1));
例子:枚举体 vs 结构体
fn main() {
#[derive(Debug)]
enum Message {
Quit, // 没有关联任何数据
Move { x: i32, y: i32 }, // 包含一个匿名结构体,或者把整行看作一个常规结构体
Write(String), // 包含单独一个 String,或者把整行看作一个元组结构体
ChangeColor(i32, i32, i32), // 包含三个 i32,或者把整行看作一个元组结构体
}
let message_quit = Message::Quit;
let message_move = Message::Move { x: 1, y: 2 };
let message_write = Message::Write("aaa".to_string());
let message_chagecolor = Message::ChangeColor(0, 0, 0);
println!(
"{:#?}\n{:#?}\n{:#?}\n{:#?}",
message_quit, message_move, message_write, message_chagecolor
);
// 上面的变量转换成结构体的写法,明显很繁琐,如果加上 impl 那就更繁琐了
// 而且没有体现变量的关联性,或者说只有用名称才能看出这四个变量是有关联的
// 结构体+枚举体可以很好地体现面向对象编程的优势
#[derive(Debug)]
struct QuitMessage; // 类单元结构体
#[derive(Debug)]
struct MoveMessage {
x: i32,
y: i32,
}
#[derive(Debug)]
struct WriteMessage(String); // 元组结构体
#[derive(Debug)]
struct ChangeColorMessage(i32, i32, i32); // 元组结构体
let struct_quit = QuitMessage;
let struct_move = MoveMessage { x: 1, y: 2 };
let struct_write = WriteMessage("aaa".to_string());
let struct_changecolor = ChangeColorMessage(0, 0, 0);
println!(
"{:#?}\n{:#?}\n{:#?}\n{:#?}",
struct_quit, struct_move, struct_write, struct_changecolor
);
}
2. 类似 C 语言风格来定义和使用枚举体:
![allow(unused)]
// 拥有隐式辨别值(implicit discriminator,从 0 开始)的 enum
[derive(Debug)]
enum Number { Zero, One, Two, }
// 拥有显式辨别值(explicit discriminator)的 enum
[derive(Debug)]
enum Color { Red = 0xff0000, Green = 0x00ff00, Blue = 0x0000ff, }
fn main() {
// enum
可以转成整型。
println!(“zero is {:?}”, Number::Zero); // zero is Zero
println!(“zero is {}”, Number::Zero as i32); // zero is 0
println!(“one is {}”, Number::One as i32); //zero is 0
println!("roses are #{:06x}", Color::Red as i32); //roses are #ff0000
println!("roses are {}", Color::Red as i32); // roses are 16711680
println!("violets are #{:06x}", Color::Blue as i32); // violets are #0000ff
println!("violets are {}", Color::Blue as i32); //violets are 255
}
# match 控制流
例子:硬币名称与面额大小
![allow(unused)]
fn main() {
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
// --snip--
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
}
}
}
println!("{}", value_in_cents(Coin::Quarter(UsState::Alaska)));
}
针对 `Option<i32>` 类型的元素进行加 1 操作:
fn plus_one(x: Option
let five = Some(5); let six = plus_one(five); // Some(6) let none = plus_one(None); // None
- match 匹配必须是又穷尽的 (exhuasted),同样,枚举体成员也必须有穷尽。
- match 的所有结果必须类型一致,如果函数无显式返回值对应于 `()` 的 unit 值。
- match 中常使用 `_` 通配符,匹配所有之前没有指定的可能的值。
let someu8_value = 0u8; // u8 可以拥有 0 到 255 的有效的值 // 可以使用 通配符,表示列出的值剩下的全部 match someu8_value { 1 => println!(“one”), 3 => println!(“three”), 5 => println!(“five”), 7 => println!(“seven”), => (), }
# if let else 控制流
`if let 模式 = 表达式` 语法可以用来处理 **只匹配一个模式** #todo:模式# 的值而忽略其他模式的情况:`let some_u8_value = Some(0u8);`
match someu8_value { Some(3) => println!(“three”), => (), }
等价于
if let Some(3) = some_u8_value { println!(“three”); }
可以认为 `if let` 是 `match` 的一个语法糖,它当值匹配某一模式时执行代码而忽略所有其他值。如果针对 `_` 模式进行更多操作,可以使用 `else` 表达式
if let 模式 = 表达式 { // to do 1 } else { // to do 2 }
等价于
match 表达式 { 模式 => {// to do 1}, _ => {// to do 2}, }
# Option 枚举类型
enum Option
在编程过程中,某个参数或者变量的值**因为某种原因目前无效(比如类型不正确、计算不正确)或缺失**,这在其他的语言中用“**空**值”来描述。空值 (Null) 是一个值,它代表没有值。在有空值的语言中,变量总是这两种状态之一:空值和非空值。经过多年的编程实践,Tony Hoare,null 的发明者,于 2009 年发表主题为 “Null References: The Billion Dollar Mistake” 的演讲,这显示出空值引发的错误代价多么沉重。<br />而 Rust 限制了空值的泛滥以增加 Rust 代码的安全性:<br />`Option<T>` 和 `T` 是不同的类型,在对 `Option<T>` 进行 `T` 的运算之前必须将其转换为 `T`。通常这能帮助我们捕获到空值最常见的问题之一:假设某值不为空但实际上为空的情况。<br />不再担心会错误的假设一个非空值,会让你对代码更加有信心。对于一个可能为空的值,你必须要显式的将其放入对应类型的 `Option<T>` 中。接着,当使用这个值时,必须明确的处理值为空的情况。只要一个值不是 `Option<T>` 类型,你就可以安全的认定它的值不为空。<br />一个值要么是某个值要么什么都不是,也就是存在或者不存在两种“选择”。从成员来看:
- Some:表示传入元素的值存在、且符合某个类型 (An element of type `T` was found)
- None:表示传入元素的值不存在、或不符合某个类型 (No element was found)
---
Optional values.<br />Type [`Option`](1e165541ab5180a303337e6f7bfa6c0f) represents an optional value: every [`Option`](1e165541ab5180a303337e6f7bfa6c0f) is either [`Some`](https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some) and contains a value, or [`None`](https://doc.rust-lang.org/std/option/enum.Option.html#variant.None), and does not. [`Option`](1e165541ab5180a303337e6f7bfa6c0f) types are very common in Rust code, as they have a number of uses:
- Initial values
- Return values for functions that are not defined over their entire input range (partial functions)
- Return value for otherwise reporting simple errors, where [`None`](https://doc.rust-lang.org/std/option/enum.Option.html#variant.None) is returned on error
- Optional struct fields
- Struct fields that can be loaned or "taken"
- Optional function arguments
- Nullable pointers
- Swapping things out of difficult situations
有多种方式来处理 `Option` 类型:
## 显式处理
- 使用 `match` 匹配情况,针对不同的 Some 和唯一的 None 进行详细地处理
- method `unwarp_or(value)`:Some 类型时返回内部的元素,None 类型时返回填入的 value
- method `unwrap_or_else(|| expr)`:Some 类型时返回内部的元素,None 类型时返回填入的表达式的值,优势在于惰性求值
- method `unwrap_or_default()`:None 时返回该类型默认值,数字类默认值为0,字符串默认为空串,布尔默认为 false,自定义类型可手动设置 [defalut](https://doc.rust-lang.org/std/default/trait.Default.html#tymethod.default)
## 隐式处理
Some 类型时返回内部的元素,None 类型时直接让程序 panic (中断运行并强制退出)。支持的 method 有:
- `unwarp()`:最常用、简单粗暴的隐式处理,要么把元素取出来,要么 panic
- `expect("panic info")`:panic 的时候自定义提示信息
- `?` 运算符:#todo#
struct Person {
job: Option
[derive(Clone, Copy)]
struct Job {
phone_number: Option
[derive(Clone, Copy)]
struct PhoneNumber {
area_code: Option
impl Person {
// Gets the area code of the phone number of the person’s job, if it exists.
fn work_phone_area_code(&self) -> Optionmatch
statements without the ?
operator.
// It would take a lot more code - try writing it yourself and see which
// is easier.
self.job?.phone_number?.area_code
}
}
fn main() { let p = Person { job: Some(Job { phone_number: Some(PhoneNumber { area_code: Some(61), number: 439222222, }), }), };
assert_eq!(p.work_phone_area_code(), Some(61));
}
参考:[rust-by-example/option_unwrapl](05a918fb8e6a6ef0f9db36163c3aa480)、[rust-by-example/question_mark.html](cbda5ca5cba7d66121b5b140e1c00acb)
## 组合器 (combinator)
combinator:[https://learning-rust.github.io/docs/e6.combinators.html](42d8cd6232cd87ccabe20304b09a9b95)
> Combinators are higher-order functions that apply only functions and earlier defined combinators to provide a result from its arguments. They can be used to **manage control flow in a modular fashion**. (模块化方式管理控制流)
> --doc.rust-lang.org/reference/glossary.html#combinator
### map
`map(|...| ...)` 方法让 `Some -> Some`和`None -> None` 映射更简单,使用多重链式 map 可以让繁琐嵌套的 match 结构更简洁灵活。
![allow(dead_code)]
[derive(Debug)] enum Food { Apple, Carrot, Potato }
[derive(Debug)] struct Peeled(Food);
[derive(Debug)] struct Chopped(Food);
[derive(Debug)] struct Cooked(Food);
// Peeling food. If there isn’t any, then return None
.
// Otherwise, return the peeled food.
fn peel(food: Option
// Chopping food. If there isn’t any, then return None
.
// Otherwise, return the chopped food.
fn chop(peeled: Option
// Cooking food. Here, we showcase map()
instead of match
for case handling.
fn cook(chopped: Option
// A function to peel, chop, and cook food all in sequence.
// We chain multiple uses of map()
to simplify the code.
fn process(food: Option
// Check whether there’s food or not before trying to eat it!
fn eat(food: Option
fn main() { let apple = Some(Food::Apple); let carrot = Some(Food::Carrot); let potato = None;
let cooked_apple = cook(chop(peel(apple)));
let cooked_carrot = cook(chop(peel(carrot)));
// Let's try the simpler looking `process()` now.
let cooked_potato = process(potato);
eat(cooked_apple);
eat(cooked_carrot);
eat(cooked_potato);
}
参考:[https://doc.rust-lang.org/rust-by-example/error/option_unwrap/map.html](776edbfd73b8c1609b7c9f5469f95c90)
### and_then
![allow(dead_code)]
[derive(Debug)] enum Food { CordonBleu, Steak, Sushi }
[derive(Debug)] enum Day { Monday, Tuesday, Wednesday }
// We don’t have the ingredients to make Sushi.
fn haveingredients(food: Food) -> Option
// We have the recipe for everything except Cordon Bleu.
fn haverecipe(food: Food) -> Option
// To make a dish, we need both the recipe and the ingredients.
// We can represent the logic with a chain of match
es:
fn cookable_v1(food: Food) -> Option
// This can conveniently be rewritten more compactly with and_then()
:
fn cookable_v2(food: Food) -> Option
fn eat(food: Food, day: Day) { match cookable_v2(food) { Some(food) => println!(“Yay! On {:?} we get to eat {:?}.”, day, food), None => println!(“Oh no. We don’t get to eat on {:?}?”, day), } }
fn main() { let (cordon_bleu, steak, sushi) = (Food::CordonBleu, Food::Steak, Food::Sushi);
eat(cordon_bleu, Day::Monday);
eat(steak, Day::Tuesday);
eat(sushi, Day::Wednesday);
}
``` 参考:https://doc.rust-lang.org/rust-by-example/error/option_unwrap/and_then.html