枚举
eg:
enum IpAddrKind {
V4,
V6,
}
fn main() {
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
route(IpAddrKind::V4);
route(IpAddrKind::V6);
}
fn route(ip_kind: IpAddrKind) {}
和struct一起使用
fn main() {
enum IpAddrKind {
V4,
V6,
}
struct IpAddr {
kind: IpAddrKind,
address: String,
}
let home = IpAddr {
kind: IpAddrKind::V4,
address: String::from("127.0.0.1"),
};
let loopback = IpAddr {
kind: IpAddrKind::V6,
address: String::from("::1"),
};
}
更简洁的方式
fn main() {
enum IpAddr {
V4(String),
V6(String),
}
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
}
fn main() {
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
}
真实场景
#![allow(unused)]
fn main() {
struct Ipv4Addr {
// --snip--
}
struct Ipv6Addr {
// --snip--
}
enum IpAddr {
V4(Ipv4Addr),
V6(Ipv6Addr),
}
}
枚举成员类型可以是多种类型
fn main() {
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
// 结构体和枚举还有另一个相似点:就像可以使用 impl 来为结构体定义方法那样,
// 也可以在枚举上定义方法。这是一个定义于我们 Message 枚举上的叫做 call 的方法
impl Message {
fn call(&self) {
// 在这里定义方法体
}
}
let m = Message::Write(String::from("hello"));
m.call();
}
Option
enum Option<T> {
None,
Some(T),
}
Option<T>枚举,不需要显示引入作用域;使用成员也是,不需要Option::前缀,直接使用Some、None。
当有一个Some值时,我们知道存在一个值,而这个值保存在Some中。当有个None值时,在某种意义上,它跟空值具有相同的意义:并没有一个有效的值
那么,Option 为什么就比空值要好呢?
因为 Option 和 T(这里 T 可以是任何类型)是不同的类型,编译器不允许像一个肯定有效的值那样使用 Option。例如,这段代码不能编译,因为它尝试将 Option 与 i8 相加:
let x: i8 = 5;
let y: Option<i8> = Some(5);
let sum = x + y; // 编译报错
Rust不知道如何将 Option 与 i8 相加,因为他们类型不同。
i8类型,编译器能确保它总是有一个有效值。
Option类型,需要担心可能没有值。
为了拥有一个可能为空的值,必须显式的将其放入对应类型的Option<T>中;当使用这个值时,必须明确的处理为空的情况。
只要一个值不是Option<T>类型,就可以安全的认定它的值不为空
这是Rust经过深思熟虑的设计决策,来限制空值的泛滥以增加Rust代码的安全性
match表达式就是一个处理枚举的控制流结构:它会根据枚举的成员运行不同的代码。
Match控制流结构
match极为强大的控制流运算符。可以将一个值与一系列的模式相比较
模式可以由字面值、变量、通配符和其他内容构成。
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
相对于if使用的条件表达式,必须返回一个布尔值,而这里它可以是任何类型。
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
}
Coin::Nickel => 5,
}
}
#[derive(Debug)]
enum UsState {
a,
b,
// --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
}
}
}
fn main() {
value_in_cents(Coin::Quarter(UsState::a));
}
匹配 Option
fn main() {
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
}
匹配必须是可穷尽的、
fn main() {
fn plus_one(x: Option<i32>) -> Option<i32> {
match x { // 编译报错 pattern `None` not covered
Some(i) => Some(i + 1),
}
}
}
没有明确处理 None 的情况
通配模式和 _ 占位符
fn main() {
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
_ => (),
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
}
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
_a => aaa(_a), // 传参
}
fn add_fancy_hat() { }
fn remove_fancy_hat() {}
fn aaa(num_spaces: u8) {
println!("{}", num_spaces);
}
if let 简洁控制流
fn main() {
let config_max = Some(3u8);
match config_max {
Some(max) => println!("The maximum is configured to be {}", max),
_ => (),
}
}
简化:
let config_max = Some(3u8);
if let Some(max) = config_max {
println!("The maximum is configured to be {}", max);
}
if let 会失去 match 强制要求的可穷尽检查。
if let 是 match 的一个语法糖
let mut count = 0;
match coin {
Coin::Quarter(state) => println!("State quarter from {:?}!", state),
_ => count += 1,
}
let mut count = 0;
if let Coin::Quarter(state) = coin {
println!("State quarter from {:?}!", state);
} else {
count += 1;
}
包和 Create
Crate 是 Rust在编译时最小的代码单元。
rustc编译单个文件(rustc main.rs),编译器会将那个文件认作一个 crate。
crate可以包含模块,模块可以定义在其他文件,然后和crate一起编译。
crate有两种形式: 二进制项和库
二进制项可以编译为可执行程序,它们必须有一个main函数来定义当程序被执行的时候所需做的事情。目前我们创建的crate都是二进制项。
库并没有main函数,它们也不会被编译为可执行程序,它们提供一些函数之类的东西,其他项目也可以使用。
比如 rand crate就提供生成随机数
大多数crate指的是库。
crate root 是一个源文件,Rust编译器以它为起点,构建crate的根模块。
包 提供一系列功能的一个或多个crate。
一个 包 会包含一个 Cargo.toml 文件,阐述如何去构建这些crate。
Cargo 就是一个包含构建你代码的二进制项的包。
Cargo 也包含这些二进制项所依赖的库。其他项目也能用 Cargo 库来实现与 Cargo 命令行程序一样的逻辑。
从 crate 根节点开始: 当编译一个 crate, 编译器首先在 crate 根文件(通常,对于一个库 crate 而言是src/lib.rs,对于一个二进制 crate 而言是src/main.rs)中寻找需要被编译的代码。
声明模块: 在 crate 根文件中,你可以声明一个新模块;比如,你用mod garden声明了一个叫做garden的模块。编译器会在下列路径中寻找模块代码:
- 内联,在大括号中,当mod garden后方不是一个分号而是一个大括号
- 在文件 src/garden.rs
- 在文件 src/garden/mod.rs
声明子模块: 在除了 crate 根节点以外的其他文件中,你可以定义子模块。比如,你可能在src/garden.rs中定义了mod vegetables;。编译器会在以父模块命名的目录中寻找子模块代码:
- 内联,在大括号中,当mod vegetables后方不是一个分号而是一个大括号
- 在文件 src/garden/vegetables.rs
- 在文件 src/garden/vegetables/mod.rs
模块中的代码路径: 一旦一个模块是你 crate 的一部分,你可以在隐私规则允许的前提下,从同一个 crate 内的任意地方,通过代码路径引用该模块的代码。举例而言,一个 garden vegetables 模块下的Asparagus类型可以在crate::garden::vegetables::Asparagus被找到。
私有 vs 公用: 一个模块里的代码默认对其父模块私有。为了使一个模块公用,应当在声明时使用pub mod替代mod。为了使一个公用模块内部的成员公用,应当在声明前使用pub。
use 关键字: 在一个作用域内,use关键字创建了一个成员的快捷方式,用来减少长路径的重复。在任何可以引用crate::garden::vegetables::Asparagus的作用域,你可以通过 use crate::garden::vegetables::Asparagus;创建一个快捷方式,然后你就可以在作用域中只写Asparagus来使用该类型。
mod aa {
pub mod hosting {
pub fn waitlist() {}
}
}
pub fn bb() {
// 绝对路径
crate::aa::hosting::waitlist();
// 相对路径
aa::hosting::waitlist();
}
使用super起始的相对路径
fn deliver_order() {}
mod aa {
fn bb() {
cook_order();
super::deliver_order();
}
fn cook_order() {}
}
bb 函数在 aa 模块中,所以我们使用 super 进入 aa 父模块,在这里可以找到 deliver_order
// src/lib.rs
mod aa {
pub struct Breakfast {
pub toast: String,
seasonal_fruit: String,
}
impl Breakfast {
pub fn summer(toast: &str) -> Breakfast {
Breakfast {
toast: String::from(toast),
seasonal_fruit: String::from("peaches"),
}
}
}
}
pub fn bb() {
// 在夏天订购一个黑麦土司作为早餐
let mut meal = aa::Breakfast::summer("Rye");
// 改变主意更换想要面包的类型
meal.toast = String::from("Wheat");
println!("I'd like {} toast please", meal.toast);
// 如果取消下一行的注释代码不能编译;
// 不允许查看或修改早餐附带的季节水果
// meal.seasonal_fruit = String::from("blueberries");
}
因为 aa:Breakfast 结构体的 toast 字段是公共的,所以可以在 bb 中使用点号来随意读写 toast 字段。注意,不能再 bb 中使用 seasonal_fruit 字段,因为 seasonal_fruit 字段是私有的,所以该字段是不能查看和修改的。
因为 aa:Breakfast 具有私有字段,所以这个结构体需要提供一个公共的关联函数来构造 Breadkfast 的实例(这里我们命名为 summer )。如果没有这个关联函数,我们将无法在 bb 中创建 Breakfast 实例,因为不能在 bb 中设置私有字段 seasonal_fruit 的值。
如果枚举(enum)设为公有,则他的所有成员都将变为公有。
mod back_of_house {
pub enum Appetizer {
Soup,
Salad,
}
}
fn eat_at_restaurant() {
let order1 = back_of_house::Appetizer::Soup;
let order2 = back_of_house::Appetizer::Salad;
}
如果枚举成员不是公有的,那么枚举会显得用处不大;给枚举的所有成员挨个添加 pub 是很令人恼火的,因此枚举成员默认就是公有的。
结构体通常使用时,不必将它们的字段公有化,因此结构体遵循常规,内容全部是私有的,除非使用 pub 关键字。
use关键字将路径引入作用域
mod aaa {
pub mod hosting {
pub fn waitlist() {}
}
}
use crate::aaa::hosting;
pub fn eat_at_restaurant() {
hosting::waitlist();
}
use 只能创建 use 所在的特定作用域内的短路径。
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
mod customer {
pub fn eat_at_restaurant() {
// eat_at_restaurant 和 use 不在同一个作用域
hosting::add_to_waitlist(); // error use of undeclared crate or module `hosting`
}
}
相同类型——具有相同的Result
use std::fmt;
use std::io;
fn function1() -> fmt::Result {
// --snip--
Ok(())
}
fn function2() -> io::Result<()> {
// --snip--
Ok(())
}
使用 as
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --snip--
Ok(())
}
fn function2() -> IoResult<()> {
// --snip--
Ok(())
}
使用 pub use 重导出名称
不仅将一个名称导入了当前作用域,还允许别人把它导入他们自己的作用域
// restaurant
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
如果 use 前没加 pub ,外部代码想要使用 add_to_waitlist 方法,则需要 restaurant::front_of_house::hosting::add_to_waitlist() 来调用。现在使用 pub use 外部代码可以使用路径 restaurant::hosting::add_to_waitlist即可
嵌套路径来消除大量的 use 行
use std::cmp::Ordering;
use std::io;
// 简化
use std::{cmp::Ordering, io};
use std::io;
use std::io::Write;
// 简化
use std::io::{self, Write};
通过 glob 运算符将所有的公有定义引入作用域
如果希望将一个路径下 所有 公有项引入作用域,可以指定路径后跟 *,glob 运算符:
glob 运算符经常用于测试模块 tests 中,这时会将所有内容引入作用域;
模块拆分-多个文件
// src/lib.rs
mod aa;
pub use crate::aa::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
// src/aa.rs
pub mod hosting {
pub fn add_to_waitlist() {}
}
mod aa; 告诉编译器,去找 aa 文件(加载这个文件),
继续拆分
文件名:src/aa.rs
文件名:src/aa/hosting.rs
pub fn add_to_waitlist() {}
如果将 hosting.rs 放在 src 目录,编译器会认为 hosting 模块中的 hosting.rs 的代码声明于 crate 根,而不是声明为 front_of_house 的子模块。
mod 关键字声明了模块,Rust 会在与模块同名的文件中查找模块的代码。
枚举
eg:
和struct一起使用
更简洁的方式
真实场景
枚举成员类型可以是多种类型
Option
Option<T>枚举,不需要显示引入作用域;使用成员也是,不需要Option::前缀,直接使用Some、None。当有一个Some值时,我们知道存在一个值,而这个值保存在Some中。当有个None值时,在某种意义上,它跟空值具有相同的意义:并没有一个有效的值
那么,Option 为什么就比空值要好呢?
因为 Option 和 T(这里 T 可以是任何类型)是不同的类型,编译器不允许像一个肯定有效的值那样使用 Option。例如,这段代码不能编译,因为它尝试将 Option 与 i8 相加:
Rust不知道如何将 Option 与 i8 相加,因为他们类型不同。
i8类型,编译器能确保它总是有一个有效值。
Option类型,需要担心可能没有值。
为了拥有一个可能为空的值,必须显式的将其放入对应类型的
Option<T>中;当使用这个值时,必须明确的处理为空的情况。只要一个值不是
Option<T>类型,就可以安全的认定它的值不为空这是Rust经过深思熟虑的设计决策,来限制空值的泛滥以增加Rust代码的安全性
match表达式就是一个处理枚举的控制流结构:它会根据枚举的成员运行不同的代码。Match控制流结构
match极为强大的控制流运算符。可以将一个值与一系列的模式相比较模式可以由字面值、变量、通配符和其他内容构成。
相对于
if使用的条件表达式,必须返回一个布尔值,而这里它可以是任何类型。匹配 Option
匹配必须是可穷尽的、
没有明确处理
None的情况通配模式和 _ 占位符
if let 简洁控制流
简化:
if let 会失去 match 强制要求的可穷尽检查。
if let 是 match 的一个语法糖
包和 Create
Crate 是 Rust在编译时最小的代码单元。
rustc编译单个文件(rustc main.rs),编译器会将那个文件认作一个 crate。crate可以包含模块,模块可以定义在其他文件,然后和crate一起编译。
crate有两种形式: 二进制项和库
二进制项可以编译为可执行程序,它们必须有一个
main函数来定义当程序被执行的时候所需做的事情。目前我们创建的crate都是二进制项。库并没有
main函数,它们也不会被编译为可执行程序,它们提供一些函数之类的东西,其他项目也可以使用。比如
randcrate就提供生成随机数大多数crate指的是库。
crate root 是一个源文件,Rust编译器以它为起点,构建crate的根模块。
包 提供一系列功能的一个或多个crate。
一个 包 会包含一个 Cargo.toml 文件,阐述如何去构建这些crate。
Cargo 就是一个包含构建你代码的二进制项的包。
Cargo 也包含这些二进制项所依赖的库。其他项目也能用 Cargo 库来实现与 Cargo 命令行程序一样的逻辑。
从 crate 根节点开始: 当编译一个 crate, 编译器首先在 crate 根文件(通常,对于一个库 crate 而言是src/lib.rs,对于一个二进制 crate 而言是src/main.rs)中寻找需要被编译的代码。
声明模块: 在 crate 根文件中,你可以声明一个新模块;比如,你用mod garden声明了一个叫做garden的模块。编译器会在下列路径中寻找模块代码:
声明子模块: 在除了 crate 根节点以外的其他文件中,你可以定义子模块。比如,你可能在src/garden.rs中定义了mod vegetables;。编译器会在以父模块命名的目录中寻找子模块代码:
模块中的代码路径: 一旦一个模块是你 crate 的一部分,你可以在隐私规则允许的前提下,从同一个 crate 内的任意地方,通过代码路径引用该模块的代码。举例而言,一个
garden vegetables模块下的Asparagus类型可以在crate::garden::vegetables::Asparagus被找到。私有 vs 公用: 一个模块里的代码默认对其父模块私有。为了使一个模块公用,应当在声明时使用
pub mod替代mod。为了使一个公用模块内部的成员公用,应当在声明前使用pub。use 关键字: 在一个作用域内,use关键字创建了一个成员的快捷方式,用来减少长路径的重复。在任何可以引用
crate::garden::vegetables::Asparagus的作用域,你可以通过use crate::garden::vegetables::Asparagus;创建一个快捷方式,然后你就可以在作用域中只写Asparagus来使用该类型。使用super起始的相对路径
bb函数在aa模块中,所以我们使用super进入aa父模块,在这里可以找到deliver_order因为
aa:Breakfast结构体的toast字段是公共的,所以可以在bb中使用点号来随意读写toast字段。注意,不能再bb中使用seasonal_fruit字段,因为seasonal_fruit字段是私有的,所以该字段是不能查看和修改的。因为
aa:Breakfast具有私有字段,所以这个结构体需要提供一个公共的关联函数来构造Breadkfast的实例(这里我们命名为summer)。如果没有这个关联函数,我们将无法在bb中创建Breakfast实例,因为不能在bb中设置私有字段seasonal_fruit的值。如果枚举(enum)设为公有,则他的所有成员都将变为公有。
如果枚举成员不是公有的,那么枚举会显得用处不大;给枚举的所有成员挨个添加 pub 是很令人恼火的,因此枚举成员默认就是公有的。
结构体通常使用时,不必将它们的字段公有化,因此结构体遵循常规,内容全部是私有的,除非使用 pub 关键字。
use关键字将路径引入作用域
use只能创建use所在的特定作用域内的短路径。相同类型——具有相同的Result
使用 as
使用 pub use 重导出名称
不仅将一个名称导入了当前作用域,还允许别人把它导入他们自己的作用域
如果
use前没加pub,外部代码想要使用add_to_waitlist方法,则需要restaurant::front_of_house::hosting::add_to_waitlist()来调用。现在使用pub use外部代码可以使用路径restaurant::hosting::add_to_waitlist即可嵌套路径来消除大量的 use 行
通过 glob 运算符将所有的公有定义引入作用域
如果希望将一个路径下 所有 公有项引入作用域,可以指定路径后跟 *,glob 运算符:
glob 运算符经常用于测试模块 tests 中,这时会将所有内容引入作用域;
模块拆分-多个文件
mod aa;告诉编译器,去找aa文件(加载这个文件),继续拆分
文件名:src/aa.rs
文件名:src/aa/hosting.rs
如果将 hosting.rs 放在 src 目录,编译器会认为
hosting模块中的 hosting.rs 的代码声明于 crate 根,而不是声明为front_of_house的子模块。mod关键字声明了模块,Rust 会在与模块同名的文件中查找模块的代码。