Rust学习(通过例子学习Rust)(4)
十七 使用macro_rule 宏
Rust 提供了一个强大的宏系统,可进行元编程
17.1语法
模式与指示符
重载
重复
17.1.1 模式与指示符
宏的参数使用一个美元符号 $ 作为前缀,并使用一个指示符(designator)来注明类型:
全部的类型或提升符有
block
expr 用于表达式
ident 用于变量名或函数名
item
literal 用于字面常量
pat (模式 pattern)
path
stmt (语句 statement)
tt (标记树 token tree)
ty (类型 type)
vis (可见性描述符)
17.1.2 重载
宏可以重载,从而接受不同的参数组合 ;分割
17.1.3 重复
宏在参数列表中可以使用 + 来表示一个参数可能出现一次或多次,使用 * 来表示该参数可能出现零次或多次。
17.2 DRY (不写重复代码)
17.3 DSL(领域专用语言)
DSL 是 Rust 的宏中集成的微型 “语言”。这种语言是完全合法的,因为宏系统会把它转换成普通的 Rust 语法树,它只不过看起来像是另一种语言而已。这就允许你为一些特定功能创造一套简洁直观的语法(当然是有限制的)
17.4 接受多个参数
可变参数接口可以接受任意数目的参数。比如说 println 就可以,其参数的数目是由格式化字符串指定的。
此章其他两本书籍已经重点学习就不在重复了
十八 错误处理
在 Rust 中有多种处理错误的方式
显式的 panic 主要用于测试,以及处理不可恢复的错误
Option 类型是为了值是可选的、或者缺少值并不是错误的情况准备的
当错误有可能发生,且应当由调用者处理时,使用 Result
18.1 panic
panic!
18.2 Option 和 unwrap
在标准库(std)中有个叫做 Option(option 中文意思是 “选项”)的枚举类型,用于有 “不存在” 的可能性的情况。它表现为以下两个 “option”(选项)中的一个:
Some(T):找到一个属于 T 类型的元素
None:找不到相应元素
你可以使用 match 语句来解开 Option,但使用 ? 运算符通常会更容易
match 是处理 Option 的一个可用的方法,但你会发现大量使用它会很繁琐,特别是当操作只对一种输入是有效的时。这时,可以使用组合算子(combinator),以模块化的风格来管理控制流。
#![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);
// 削皮。如果没有食物,就返回 `None`。否则返回削好皮的食物。
fn peel(food: Option<Food>) -> Option<Peeled> {
match food {
Some(food) => Some(Peeled(food)),
None => None,
}
}
// 切食物。如果没有食物,就返回 `None`。否则返回切好的食物。
fn chop(peeled: Option<Peeled>) -> Option<Chopped> {
match peeled {
Some(Peeled(food)) => Some(Chopped(food)),
None => None,
}
}
// 烹饪食物。这里,我们使用 `map()` 来替代 `match` 以处理各种情况。
fn cook(chopped: Option<Chopped>) -> Option<Cooked> {
chopped.map(|Chopped(food)| Cooked(food))
}
// 这个函数会完成削皮切块烹饪一条龙。我们把 `map()` 串起来,以简化代码。
fn process(food: Option<Food>) -> Option<Cooked> {
food.map(|f| Peeled(f))
.map(|Peeled(f)| Chopped(f))
.map(|Chopped(f)| Cooked(f))
}
// 在尝试吃食物之前确认食物是否存在是非常重要的!
fn eat(food: Option<Cooked>) {
match food {
Some(food) => println!("Mmm. I love {:?}", food),
None => println!("Oh no! It wasn't edible."),
}
}
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)));
// 现在让我们试试看起来更简单的 `process()`。
let cooked_potato = process(potato);
eat(cooked_apple);
eat(cooked_carrot);
eat(cooked_potato);
}
组合算子:and_then
#![allow(dead_code)]
#[derive(Debug)] enum Food { CordonBleu, Steak, Sushi }
#[derive(Debug)] enum Day { Monday, Tuesday, Wednesday }
// 我们没有制作寿司所需的原材料(ingredient)(有其他的原材料)。
fn have_ingredients(food: Food) -> Option<Food> {
match food {
Food::Sushi => None,
_ => Some(food),
}
}
// 我们拥有全部食物的食谱,除了法国蓝带猪排(Cordon Bleu)的。
fn have_recipe(food: Food) -> Option<Food> {
match food {
Food::CordonBleu => None,
_ => Some(food),
}
}
// 要做一份好菜,我们需要原材料和食谱。
// 我们可以借助一系列 `match` 来表达这个逻辑:
fn cookable_v1(food: Food) -> Option<Food> {
match have_ingredients(food) {
None => None,
Some(food) => match have_recipe(food) {
None => None,
Some(food) => Some(food),
},
}
}
// 也可以使用 `and_then()` 把上面的逻辑改写得更紧凑:
fn cookable_v2(food: Food) -> Option<Food> {
have_ingredients(food).and_then(have_recipe)
}
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);
}
18.3 Result
Result 是 Option 类型的更丰富的版本,描述的是可能的错误而不是可能的不存在。
Ok:找到 T 元素
Err:找到 E 元素,E 即表示错误的类型。
Result 的map
use std::num::ParseIntError;
// 就像 `Option` 那样,我们可以使用 `map()` 之类的组合算子。
// 除去写法外,这个函数与上面那个完全一致,它的作用是:
// 如果值是合法的,计算其乘积,否则返回错误。
fn multiply(first_number_str: &str, second_number_str: &str) -> Result<i32, ParseIntError> {
first_number_str.parse::<i32>().and_then(|first_number| {
second_number_str.parse::<i32>().map(|second_number| first_number * second_number)
})
}
fn print(result: Result<i32, ParseIntError>) {
match result {
Ok(n) => println!("n is {}", n),
Err(e) => println!("Error: {}", e),
}
}
fn main() {
// 这种情况下仍然会给出正确的答案。
let twenty = multiply("10", "2");
print(twenty);
// 这种情况下就会提供一条更有用的错误信息。
let tt = multiply("t", "2");
print(tt);
}
给Result 起别名
当我们要重用某个 Result 类型时,该怎么办呢?回忆一下,Rust 允许我们创建别名。若某个 Result 有可能被重用,我们可以方便地给它取一个别名。
提前返回
在上一个例子中,我们显式地使用组合算子处理了错误。另一种处理错误的方式是使用 match 语句和提前返回(early return)的结合。
有时我们只是想 unwrap 且避免产生 panic。到现在为止,对 unwrap 的错误处理都在强迫我们一层层地嵌套,然而我们只是想把里面的变量拿出来。? 正是为这种情况准备的。? 之前是try! 宏
18.4 处理多种错误
18.5 遍历result 省略
十九. 标准库的学习
后续章节建议参考其他书籍学习,书本内容比较浅,参考意义不大;
Rust 的基础学习终于告一断落,后续主要是关于前后端框架和网络相关学习;