diff --git a/.vscode/settings.json b/.vscode/settings.json index 638c8e3..caa2e2b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { "rust-analyzer.linkedProjects": [ - "./17.macro_rules/Cargo.toml" + "./19.Std Library types/Cargo.toml" ] } diff --git a/18.Error Handling/Cargo.lock b/18.Error Handling/Cargo.lock new file mode 100644 index 0000000..1d0f6fd --- /dev/null +++ b/18.Error Handling/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "panic" +version = "0.1.0" diff --git a/18.Error Handling/Cargo.toml b/18.Error Handling/Cargo.toml new file mode 100644 index 0000000..f484c90 --- /dev/null +++ b/18.Error Handling/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "panic" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/18.Error Handling/src/main.rs b/18.Error Handling/src/main.rs new file mode 100644 index 0000000..b8410f1 --- /dev/null +++ b/18.Error Handling/src/main.rs @@ -0,0 +1,1143 @@ +//! +//! 错误处理 +//! +//! 错误处理是为了处理程序中可能出现的错误,比如说读取文件的过程中出错,如果继续使用这个错误的结果 +//! 进行后续的处理肯定是错误的,应该明确的处理这些错误,并且避免其他部分使用这种错误的数据。 +//! +//! Rust 中有很多种方法来处理错误,下面的章节会逐步讲解这些处理方法,它们之间会有一些细微的差别, +//! 和不同的应用场景,下面是一些错误处理的经验。 +//! +//! 在测试用例的代码中可以使用 `panic!` 来处理不可恢复性的错误让测试使用具体的信息报出错误。 +//! 在设计阶段可以也可以使用 `panic` 的提示来告诉用户某些方法还没有实现。 +//! 在测试阶段使用 `panic!` 是非常有效的一种错误提示。 +//! +//! 还有一种类型是 `Option` 他代表了一种可选地数据类型,当某个值是可选地时候缺少具体的值不应该是一个错误, +//! 常见的场景是目录当要获取 `/` 或 `C:` 的父目录的时候是获取不到的,因为当前目录已经是根目录了,而这个操作不应该 `panic!`。 +//! 当使用 `Option` 类型的时候如果能确定值一定存在则可以使用 `unwrap` 方法来获取值, +//! 如果说当值不存在的时候必须要 `panic!` 的时候可以使用 `expect` 方法来快捷的指定 `panic!` 的错误信息。 +//! +//! 如果使用方必须处理可能出现的错误的情况下可以使用 `Result` 类型,使用方就可以是 `expect` 和 `unwrap` 方法来处理结果 +//! (通常情况下不要使用这俩方法,因为一旦有错误产生就会 `panic` 可以使用解构语法来避免程序崩溃)。 +//! +//! 其他的错误处理情况可以参考[官方推荐的书中的错误处理方式](https://doc.rust-lang.org/book/ch09-00-error-handling.html) +//! + +/// 最简单的处理错误方法就是使用 `panic!`,它可以明确的给出错误信息和崩溃前的调用栈, +/// 可以明确的使用 `panic!` 宏来让程序马上崩溃退出。 +fn panic_() { + fn drink(beverage: &str) { + // 你不应该喝太多的含糖饮料 + if beverage == "lemonade" { + // 明确调用 `panic!` 宏来让程序崩溃退出。 + panic!("AAAaaaaa!!!!"); + } + + println!("Some refreshing {} is all I need.", beverage); + } + + drink("water"); + drink("lemonade"); + // 下面这个方法永远都不会被调用,因为上面的方法造成了 `panic!` + drink("still water"); +} + +fn abort_and_unwind() { + fn drink_with_cfg_macro(beverage: &str) { + // 你不应该喝太多的含糖饮料 + if beverage == "lemonade" { + // 使用条件宏来定义 `panic` 的行为 + // 条件宏会把所有分支的代码都编译到结果中 + if cfg!(panic = "abort") { + println!("This is not your party. Run!!!!"); + } else { + println!("Spit it out!!!!"); + } + } else { + println!("Some refreshing {} is all I need.", beverage); + } + } + + // 属性宏会在编译阶段就确定使用哪种代码,没有命中的代码不会被编译到可执行文件中 + // 使用属性宏来调整代码行为 + #[cfg(panic = "unwind")] + fn ah() { + println!("Spit it out!!!!"); + } + + // 使用属性宏来调整代码行为 + #[cfg(not(panic = "unwind"))] + fn ah() { + println!("This is not your party. Run!!!!"); + } + + fn drink_with_attr_macro(beverage: &str) { + if beverage == "lemonade" { + ah(); + } else { + println!("Some refreshing {} is all I need.", beverage); + } + } + + drink_with_attr_macro("water"); + drink_with_attr_macro("lemonade"); +} + +/// `Option` 类型和内置方法 +/// +/// `Option` 是一个枚举类型,拥有两个枚举值 `Some` 和 `None`, +/// 这两个选项可以使用 `match` 明确的处理每种可能性, +/// 或者使用 `unwrap` 来获取值,当没有值的时候会自动调用 `panic!`。 +/// 还可以使用 `expect` 方法来明确的指定没有值的时候 `panic!` 抛出的错误信息。 +/// +fn option_and_unwrap() { + // 让成年人来判断喝的东西是什么。 + fn give_adult(drink: Option<&str>) { + // 这里使用了 `match` 关键字来处理 `Option` 的每一种可能性。 + match drink { + Some("lemonade") => println!("Yuck! Too sugary."), + Some(inner) => println!("{}? How nice.", inner), + None => println!("No drink? Oh well."), + } + } + + // 如果尝试去拿要喝的东西,如果拿不到的话 `unwrap` 方法会直接 `panic!`, + // 如果拿到的喝的东西是 `lemonade` 的话也会 `panic!`。 + // 其余的都可以喝。 + fn drink(drink: Option<&str>) { + // `unwrap` 方法会检查 `drink` 是不是 `None` 如果是的话会 `panic!`。 + let inside = drink.unwrap(); + + // 这里检查是不是 `lemonade` 如果是的话也会 `panic!`。 + if inside == "lemonade" { + panic!("AAAaaaaa!!!!"); + } + + println!("I love {}s!!!!!", inside); + } + + let water = Some("water"); + let lemonade = Some("lemonade"); + let void = None; + + give_adult(water); + give_adult(lemonade); + give_adult(void); + + let coffee = Some("coffee"); + let nothing = None; + + drink(coffee); + // 这里会 `panic!` + drink(nothing); +} + +/// 使用 `?` 号操作符来获取 `Option` 中存放的数据。 +/// +/// `Option` 可以使用 `match`,`if let` 语句来获取数据,还可以使用 `?` 操作符来更快捷的获取数据, +/// 当 `x = Some(T)` 的时候可以使用 `let value = x?` 来获取 `Some(T)` 中的 `T`,也就是说 +/// 这里的 `value = T`,如果 `x = None` 的话则当前的函数会提前退出并把 `None` 返回给调用方。/// +/// +fn unpacking_options_with_question_mark() { + fn next_birthday(current_age: Option) -> Option { + // 如果 `current_age` 是 `None` 直接返回 `None`. + // 如果 `current_age` 是 `Some` 内部的 `u8` 经过计算后会赋值给 `next_age` + let next_age: u8 = current_age? + 1; + Some(format!("Next year I will be {}", next_age)) + } + + // `?` 号操作符支持链式使用 + struct Person { + job: Option, + } + + #[derive(Clone, Copy)] + struct Job { + phone_number: Option, + } + + #[derive(Clone, Copy)] + struct PhoneNumber { + area_code: Option, + number: u32, + } + + impl Person { + // 获取当前结构上的指定值。 + fn work_phone_area_code(&self) -> Option { + // 这里使用了链式的 `?` 操作符来快捷的从 `Option` 中获取数据,如果数据不存在的话则会返回 `None` + // 相比使用 `match` 语句来说 `?` 更加方便快捷 + self.job?.phone_number?.area_code + } + } + + 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)); +} + +/// `match` 可以处理 `Option`,但是当层级特别深或者链路很长的时候写起来会非常麻烦, +/// 尤其当只需要处理一个可能的有效值的时候极其麻烦。 +/// +/// 所以 `Option` 提供了一个方法是 `map`,用于只处理有值的情况。 +/// +fn combinators_map() { + #![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); + + // 使用 `match` 方法处理 `Option` + fn peel(food: Option) -> Option { + match food { + Some(food) => Some(Peeled(food)), + None => None, + } + } + + // 使用 `match` 方法处理 `Option` + fn chop(peeled: Option) -> Option { + match peeled { + Some(Peeled(food)) => Some(Chopped(food)), + None => None, + } + } + + // 这里使用 `map` 操作符来替换 `match` 关键字 + // 可以看到相同的逻辑使用 `map` 更加简单 + fn cook(chopped: Option) -> Option { + chopped.map(|Chopped(food)| Cooked(food)) + } + + // 使用链式语法一次性做多次转换 + fn process(food: Option) -> Option { + food.map(|f| Peeled(f)) + .map(|Peeled(f)| Chopped(f)) + .map(|Chopped(f)| Cooked(f)) + } + + // 使用 `match` 操作符对结果做不通的处理 + fn eat(food: Option) { + match food { + Some(food) => println!("Mmm. I love {:?}", food), + None => println!("Oh no! It wasn't edible."), + } + } + + 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 cooked_potato = process(potato); + + eat(cooked_apple); + eat(cooked_carrot); + eat(cooked_potato); +} + +/// +/// `map` 可以使用链式语法来简化 `match` 语句, +/// 但是使用 `map` 如果闭包返回的是 `Option` +/// 则会导致结果会被包装成 `Option>` +/// 因为 map 会对闭包的返回值进行包装,当链式调用很多的话可能会让代码变得不可读。 +/// +/// 所以 `Option` 还提供了另一个方法叫 `and_then` 在其他语言下叫 `flatmap`, +/// +/// 如果调用 `and_then` 的对象是一个 Option 的包装对象,则会返回包装的内容, +/// 如果是 `None` 则会直接返回 `None` +/// +/// +fn combinators_and_then() { + #![allow(dead_code)] + + #[derive(Debug)] + enum Food { + CordonBleu, + Steak, + Sushi, + } + #[derive(Debug)] + enum Day { + Monday, + Tuesday, + Wednesday, + } + + fn have_ingredients(food: Food) -> Option { + match food { + Food::Sushi => None, + _ => Some(food), + } + } + + fn have_recipe(food: Food) -> Option { + match food { + Food::CordonBleu => None, + _ => Some(food), + } + } + + fn cookable_v1(food: Food) -> Option { + match have_recipe(food) { + None => None, + Some(food) => have_ingredients(food), + } + } + + // `and_then` 的返回值就是闭包的返回值,不会进行包装。 + fn cookable_v3(food: Food) -> Option { + have_recipe(food).and_then(have_ingredients) + } + + // `map` 会对返回值进行包装,然后使用 `flatten` 方法展开一层 `Option` 的包装 + fn cookable_v2(food: Food) -> Option { + have_recipe(food).map(have_ingredients).flatten() + } + + fn eat(food: Food, day: Day) { + match cookable_v3(food) { + Some(food) => println!("Yay! On {:?} we get to eat {:?}.", day, food), + None => println!("Oh no. We don't get to eat on {:?}?", day), + } + } + + let (cordon_bleu, steak, sushi) = (Food::CordonBleu, Food::Steak, Food::Sushi); + + eat(cordon_bleu, Day::Monday); + eat(steak, Day::Tuesday); + eat(sushi, Day::Wednesday); +} + +/// +/// `Option` 有很多方法可以获取内部封装的值,如果没有值的情况下可以使用 `None` 来当做默认值。 +/// 你可以根据不同的场景来选择使用不同的方法。 +/// +/// - 是立即还是可以延迟计算? +/// - 是否需要对数据已经转移并且让原始位置清空,或者在原始的位置直接修改? +/// +fn unpacking_options_and_default() { + // `or` 方法是一个可以链式、立即计算、并且把原始值直接转移出来的方法 + { + #[derive(Debug)] + enum Fruit { + Apple, + Orange, + Banana, + Kiwi, + Lemon, + } + + let apple = Some(Fruit::Apple); + let orange = Some(Fruit::Orange); + let no_fruit: Option = None; + + let first_available_fruit = no_fruit.or(orange).or(apple); + + println!("first_available_fruit: {:?}", first_available_fruit); + // first_available_fruit: Some(Orange) + + // `or` 会同时转移调用示例和传递的参数的所有权。 + // 上面的调用中同时使用了 `apple`,`orange`,`no_fruit` 三个变量,这三个变量的所有权都被转移了。 + // 所以这里不能再使用这些变量了。 + // println!( + // "Variable apple was moved, so this line won't compile: {:?}", + // apple + // ); + // TODO: 移除注释查看错误信息 + } + + // `or_else` 方法是一个可以链式、惰性计算、并且把原始值直接转移出来的方法 + { + #[derive(Debug)] + enum Fruit { + Apple, + Orange, + Banana, + Kiwi, + Lemon, + } + + let apple = Some(Fruit::Apple); + let no_fruit: Option = None; + + // 定义默认值的闭包函数 + let get_kiwi_as_fallback = || { + println!("Providing kiwi as fallback"); + Some(Fruit::Kiwi) + }; + // 定义默认值的闭包函数 + let get_lemon_as_fallback = || { + println!("Providing lemon as fallback"); + Some(Fruit::Lemon) + }; + + let first_available_fruit = no_fruit + .or_else(get_kiwi_as_fallback) // 传递默认值闭包函数 + .or_else(get_lemon_as_fallback); // 传递默认值闭包函数 + println!("first_available_fruit: {:?}", first_available_fruit); + // first_available_fruit: Some(Kiwi) + } + + // `get_or_insert` 方法是一个立即计算,当值为 `None` 的时候在原地插入默认值的方法 + { + #[derive(Debug)] + enum Fruit { + Apple, + Orange, + Banana, + Kiwi, + Lemon, + } + + let mut my_fruit: Option = None; + let apple = Fruit::Apple; + let first_available_fruit = my_fruit.get_or_insert(apple); + println!("first_available_fruit is: {:?}", first_available_fruit); + println!("my_fruit is: {:?}", my_fruit); + // first_available_fruit is: Apple + // my_fruit is: Some(Apple) + + // 这里会报错因为 `apple` 的所有权被转移了。 + // println!("Variable named `apple` is moved: {:?}", apple); + // TODO: 解除注释查看错误 + } + + // `get_or_insert_with` 方法是一个惰性计算,并且值为 `None` 的时候原地修改值的方法。 + { + #[derive(Debug)] + enum Fruit { + Apple, + Orange, + Banana, + Kiwi, + Lemon, + } + let mut my_fruit: Option = None; + + // 定义默认值函数 + let get_lemon_as_fallback = || { + println!("Providing lemon as fallback"); + Fruit::Lemon + }; + + // 给出默认值函数 + let first_available_fruit = my_fruit.get_or_insert_with(get_lemon_as_fallback); + println!("first_available_fruit is: {:?}", first_available_fruit); + println!("my_fruit is: {:?}", my_fruit); + // Providing lemon as fallback + // first_available_fruit is: Lemon + // my_fruit is: Some(Lemon) + + let mut my_apple = Some(Fruit::Apple); + // 如果调用 `get_or_insert_with` 方法的值不为 `None` 的话,则直接使用原始值。 + let should_be_apple = my_apple.get_or_insert_with(get_lemon_as_fallback); + println!("should_be_apple is: {:?}", should_be_apple); + println!("my_apple is unchanged: {:?}", my_apple); + // should_be_apple is: Apple + // my_apple is unchanged: Some(Apple) + } +} + +/// +/// `Result` 对象 +/// `Result` 是 `Option` 对象更加明确的对于结果的描述对象,该对象使用了 `Result` 签名来描述, +/// 更加明确的描述了一个操作的结果是正确还是错误。 +/// +/// 下面是 `Result` 对象的两个可能的值 +/// +/// - `Ok(T)` 拥有一个正确的结果 +/// - `Err(E)` 拥有一个错误的结果 +/// +/// `Result` 和 `Option` 非常相似,拥有很多相同的方法比如说 `unwrap`,当结果是一个 `Err(E)` 的时候则会调用 `panic!`。 +/// 大多数 `Option` 上的方法在 `Result` 都可以直接使用。 +/// +fn result() { + fn multiply(first_number_str: &str, second_number_str: &str) -> i32 { + // 尝试 `unwrap` `parse` 方法返回的结果。 + let first_number = first_number_str.parse::().unwrap(); + let second_number = second_number_str.parse::().unwrap(); + first_number * second_number + } + + let twenty = multiply("10", "2"); + println!("double is {}", twenty); + + // 这里会 `panic!` 因为 `t` 不能被转换成数字。 + // let tt = multiply("t", "2"); + // println!("double is {}", tt); + // TODO: 移除注释查看错误 +} + +/// +/// 在代码中 `main` 函数是一个非常特殊的函数,该函数可以和 `C/C++` 的行为一样拥有返回值, +/// 在 `Rust` 中 `main` 函数可以通过 `Result` 对象来定义错误的返回值,该错误需要能被 `{:?}` +/// 符号格式化,也就是说需要实现 `Debug` 特性。 +/// +/// +/// 注意 下面这个函数中的代码只能在独立的工程中使用,因为一个程序只允许存在一个 `main` 函数。 +/// +fn using_result_in_main() { + // 使用这些代码重新创建一个工程来查看 `main` 函数拥有返回值的效果 + use std::num::ParseIntError; + fn main() -> Result<(), ParseIntError> { + let number_str = "10"; + let number = match number_str.parse::() { + Ok(number) => number, + Err(e) => return Err(e), + }; + println!("{}", number); + Ok(()) + } +} + +/// +/// `Result` 对象的 `map` 方法 +/// +/// 在 `fn result();` 函数中的 `multiply` 方法因为使用了 `unwrap` 导致了可能会让程序崩溃,所以代码不够健壮, +/// 通常我们更希望函数会把错误告知给调用方,让调用方来决定应该如何处理对应的错误。 +/// +/// 第一点我们需要先知道错误的类型是什么,然后决定对应的错误类型进行针对性的处理, +/// 就像是 `multiply` 函数中可能出错的方法是 `parse` 所以我们可以通过查看 `parse` 的定义来查看错误的类型。 +/// +fn map_for_result() { + use std::num::ParseIntError; + + // 通过查看 `parse` 方法找到了对应的错误类型是 `ParseIntError`, + // 然后我们修改代码为 `match` 然后在修改函数签名和代码逻辑,当出现错误的时候直接返回错误。 + fn multiply_with_match( + first_number_str: &str, + second_number_str: &str, + ) -> Result { + match first_number_str.parse::() { + Ok(first_number) => match second_number_str.parse::() { + Ok(second_number) => Ok(first_number * second_number), + Err(e) => Err(e), + }, + Err(e) => Err(e), + } + } + + // 大多数的 `Option` 方法都可以使用在 `Result` 上, + // 所以这里可以使用这些快捷方法来实现相同的功能,但是需要的代码更少,逻辑更清晰 + fn multiply_with_combinators( + first_number_str: &str, + second_number_str: &str, + ) -> Result { + first_number_str.parse::().and_then(|first_number| { + second_number_str + .parse::() + .map(|second_number| first_number * second_number) + }) + } + + fn print(result: Result) { + match result { + Ok(n) => println!("n is {}", n), + Err(e) => println!("Error: {}", e), + } + } + // 这里拿到一个结果的包装对象,包含了可能的结果。 + let twenty = multiply_with_match("10", "2"); + print(twenty); + + // 同上 + let tt = multiply_with_combinators("t", "2"); + + // 通过辅助函数来输出结果的内容 + print(tt); +} + +/// +/// 使用类型别名来重新定义 `Result` 类型 +/// 因为 `Result` 的签名要求我们需要明确的给出两个泛型类型才能正常使用,这样的话每次使用的时候会有点麻烦 +/// 我们可以通过类型别名来缩减代码,重新定义一个只有一个泛型参数的 `Result` 类型来简化我们的代码。 +/// +/// 在标准库中有一个非常常用的 `Result` 类型是 `std::io::Result` 实际上就是重新对 `std::result::Result` +/// 定义了一个别名 `type std::io::Result = std::result::Result` +/// +fn aliases_for_result() { + use std::num::ParseIntError; + + // 定义了一个泛型的别名,把 `ParseIntError` 直接当做了 `Result` 的错误类型。 + type AliasedResult = Result; + + // 这里修改返回值定义为我们自己定义的别名 + fn multiply(first_number_str: &str, second_number_str: &str) -> AliasedResult { + first_number_str.parse::().and_then(|first_number| { + second_number_str + .parse::() + .map(|second_number| first_number * second_number) + }) + } + + // 这里修改输入类型为自定义的别名。 + fn print(result: AliasedResult) { + match result { + Ok(n) => println!("n is {}", n), + Err(e) => println!("Error: {}", e), + } + } + + print(multiply("10", "2")); + print(multiply("t", "2")); +} + +/// 在前面的代码中我们使用了 `match` 关键字来处理各种情况, +/// 实际上在 `match` 中还可以通过使用 `return` 关键字来提前退出函数。 +fn eraly_result() { + use std::num::ParseIntError; + + fn multiply(first_number_str: &str, second_number_str: &str) -> Result { + let first_number = match first_number_str.parse::() { + Ok(first_number) => first_number, + Err(e) => return Err(e), // 使用 `return` 关键字来提前退出函数的执行并返回一个值 + }; + + let second_number = match second_number_str.parse::() { + Ok(second_number) => second_number, + Err(e) => return Err(e), // 使用 `return` 关键字来提前退出函数的执行并返回一个值 + }; + + Ok(first_number * second_number) + } + + fn print(result: Result) { + match result { + Ok(n) => println!("n is {}", n), + Err(e) => println!("Error: {}", e), + } + } + + print(multiply("10", "2")); + print(multiply("t", "2")); +} + +/// +/// 通常情况下我们只需要获取内部封装的数据,如果遇到错误的话就直接返回错误,而不是让程序崩溃 +/// 而 `?` 操作符就是专门为了解决这个场景而设计的, +/// +/// 来看看使用 `?` 操作符来简化之前写的这个代码。 +/// +fn intorducing_question_mark() { + // `?` 操作符 + { + use std::num::ParseIntError; + + fn multiply(first_number_str: &str, second_number_str: &str) -> Result { + // 使用 `?` 操作符替换掉 `match` 或 `unwrap` 等等的一系列操作符 + let first_number = first_number_str.parse::()?; + let second_number = second_number_str.parse::()?; + + Ok(first_number * second_number) + } + + fn print(result: Result) { + match result { + Ok(n) => println!("n is {}", n), + Err(e) => println!("Error: {}", e), + } + } + + print(multiply("10", "2")); + print(multiply("t", "2")); + } + + // // `?` 操作符的前身 `try!` 宏 + // { + // // 这个地方会报错因为 `try!` 宏已经被 `?` 操作符取代了, + // // 想要能正常编译需要修改 `Cargo.toml` 中 `[package]` 下面的 `edition` 字段为 `2015` + // use std::num::ParseIntError; + + // fn multiply(first_number_str: &str, second_number_str: &str) -> Result { + // let first_number = try!(first_number_str.parse::()); + // let second_number = try!(second_number_str.parse::()); + + // Ok(first_number * second_number) + // } + + // fn print(result: Result) { + // match result { + // Ok(n) => println!("n is {}", n), + // Err(e) => println!("Error: {}", e), + // } + // } + // print(multiply("10", "2")); + // print(multiply("t", "2")); + // } +} + +/// +/// 前面的示例中使用了很多简便的方法,也讲解了两个很重要的类型 `Option` 和 `Result`, +/// `Option` 可以和 `Option` 交互,`Result` 可以和 `Result` 交互。 +/// +/// 但是有些情况下我们可能需要 `Option` 和 `Result` 之间进行交互,或者 `Result` +/// 和 `Result` 进行交互,在这种情况下我们希望以一种可组合且易于维护的方式来处理不同的错误类型。 +/// +/// 下面这个例子中就出现了两种不同的错误类型。 +/// +fn multiple_error_types() { + fn double_first(vec: Vec<&str>) -> i32 { + let first = vec.first().unwrap(); // 错误1 + 2 * first.parse::().unwrap() // 错误2 + } + + let numbers = vec!["42", "93", "18"]; + let empty = vec![]; + let strings = vec!["tofu", "93", "18"]; + + println!("The first doubled is {}", double_first(numbers)); + + println!("The first doubled is {}", double_first(empty)); + // 错误 1: 输入的数据是空的 + + println!("The first doubled is {}", double_first(strings)); + // 错误 2: 第一个元素不能被转换成数字 +} + +/// +/// 最简单的方法是直接把两个对象嵌套起来使用 +/// +fn pulling_results_out_of_options() { + // `Option>` 包装 + { + use std::num::ParseIntError; + + fn double_first(vec: Vec<&str>) -> Option> { + // 使用 `map` 方法会把闭包的返回值包装成 `Option`, + // 闭包中返回的是 `Result` 类型 + vec.first().map(|first| first.parse::().map(|n| 2 * n)) + } + + let numbers = vec!["42", "93", "18"]; + let empty = vec![]; + let strings = vec!["tofu", "93", "18"]; + + println!("The first doubled is {:?}", double_first(numbers)); + + println!("The first doubled is {:?}", double_first(empty)); + // 错误 1: 输入是空的 + + println!("The first doubled is {:?}", double_first(strings)); + // 错误 2: 第一个元素不能转换为数字 + } + + // `Result, E>` 包装 + { + use std::num::ParseIntError; + + fn double_first(vec: Vec<&str>) -> Result, ParseIntError> { + // `map` 方法包装成 `Option`,闭包中返回 `Result` + let opt = vec.first().map(|first| first.parse::().map(|n| 2 * n)); + + // 使用 `Option` 的 `map_or` 方法解包并做反向包装 + opt.map_or(Ok(None), |r| r.map(Some)) + } + + let numbers = vec!["42", "93", "18"]; + let empty = vec![]; + let strings = vec!["tofu", "93", "18"]; + + println!("The first doubled is {:?}", double_first(numbers)); + println!("The first doubled is {:?}", double_first(empty)); + println!("The first doubled is {:?}", double_first(strings)); + } +} + +/// +/// 自定义错误类型 +/// +/// 大多数时候都会选择定义一个错误的类型,这个错误类型包含所有可能出现的错误, +/// 通常情况下定义的错误约定如下 +/// +/// - 在同一个类型下定义多种可能出现的错误 +/// - 能给用户提供一个明确的错误信息 +/// - 可以非常容易的通过 `if let` 或者 `match` 语句进行判断 +/// - 推荐 `Err(EmptyVec)` +/// - 不推荐 `Err("Please use a vector with at least one element".to_owned())` +/// - 可以储存明确的错误信息 +/// - 推荐 `Err(BadChar(c, position))` +/// - 不推荐 `Err("+ cannot be used here".to_owned())` +/// - 可以组合使用 +/// +fn defining_an_error_type() { + use std::fmt; + + // 定义一个类型别名 + type Result = std::result::Result; + + // 定义错误类型 + #[derive(Debug, Clone)] + struct DoubleError; + + // 对以错误类型实现格式化特性,让使用方可以直接把错误以文字形式展示给用户。 + impl fmt::Display for DoubleError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "invalid first item to double") + } + } + + fn double_first(vec: Vec<&str>) -> Result { + vec.first() + // `ok_or` 方法转换 `Option` 为 `Result` 修改错误为我们定义的类型 + .ok_or(DoubleError) + .and_then(|s| { + s.parse::() + // 把错误转换成自己的类型 + .map_err(|_| DoubleError) + .map(|i| 2 * i) + }) + } + + fn print(result: Result) { + match result { + Ok(n) => println!("The first doubled is {}", n), + Err(e) => println!("Error: {}", e), + } + } + + let numbers = vec!["42", "93", "18"]; + let empty = vec![]; + let strings = vec!["tofu", "93", "18"]; + + print(double_first(numbers)); + print(double_first(empty)); + print(double_first(strings)); +} + +/// +/// 错误装箱 +/// +/// 当使用了自定义错误的时候会产生比较多额外的转化代码,可以通过 `Box` 来快速的包装错误转换逻辑, +/// 这里有一个缺点就是当使用 `Box` 对象就代表我们的对象在编译阶段是无法确定大小的。 +/// +/// 标准库中提供了一个特性是 `std::error:Error` 我们实现这个特性就可以让自定义错误可以自动装箱 +/// +fn boxing_errors() { + use std::error; + use std::fmt; + + // 修改类型别名 `Box`. + type Result = std::result::Result>; + + #[derive(Debug, Clone)] + struct EmptyVec; + + // 实现 Display 特性,因为 `std::error::Error` 特性要求必须要实现这个特性 + impl fmt::Display for EmptyVec { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "invalid first item to double") + } + } + + // 实现标准错误特性来提供自动装箱的能力 + impl error::Error for EmptyVec {} + + fn double_first(vec: Vec<&str>) -> Result { + vec.first() + // `ok_or` 方法转换 `Option` 为 `Result` + .ok_or_else(|| EmptyVec.into()) // 使用自动装箱提供的转换能力 + .and_then(|s| { + s.parse::() + // 因为 `ParseIntError` 同样也实现了标准错误 `std::error::Error` 所以这里实际上是为了把 `ParseIntError` 转换成 `dyn Error` + .map_err(|e| e.into()) + .map(|i| 2 * i) + }) + } + + fn print(result: Result) { + match result { + Ok(n) => println!("The first doubled is {}", n), + Err(e) => println!("Error: {}", e), + } + } + + let numbers = vec!["42", "93", "18"]; + let empty = vec![]; + let strings = vec!["tofu", "93", "18"]; + + print(double_first(numbers)); + print(double_first(empty)); + print(double_first(strings)); +} + +/// +/// `?` 操作符的其他用法 +/// +/// `?` 操作符只能在 `Result` 对象上使用,那么对于 `Option` 对象我们可以使用 +/// `ok_or`、`ok_or_else` 两个方法把 `Option` 转换成 `Result` 对象。 +/// +fn other_uses_of_question_mark() { + use std::error; + use std::fmt; + + // 修改错误类型为 `Box`. + type Result = std::result::Result>; + + #[derive(Debug)] + struct EmptyVec; + + impl fmt::Display for EmptyVec { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "invalid first item to double") + } + } + + impl error::Error for EmptyVec {} + + // 这里使用 `?` 对 `Result` 进行数据提取,提取失败的话直接返回原始错误。 + // 进行修改后 代码简洁了很多,也很清晰。 + fn double_first(vec: Vec<&str>) -> Result { + // `first()` 方法返回的是 `Option`,通过 `ok_or` 方法转换成 `Result` + let first = vec.first().ok_or(EmptyVec)?; + let parsed = first.parse::()?; + Ok(2 * parsed) + } + + fn print(result: Result) { + match result { + Ok(n) => println!("The first doubled is {}", n), + Err(e) => println!("Error: {}", e), + } + } + + let numbers = vec!["42", "93", "18"]; + let empty = vec![]; + let strings = vec!["tofu", "93", "18"]; + + print(double_first(numbers)); + print(double_first(empty)); + print(double_first(strings)); +} + +/// +/// 最终完整的错误解决方案应该是把其他类型的错误封装到自己定义的错误中 +/// +fn wrapping_errors() { + use std::error; + use std::error::Error; + use std::fmt; + use std::num::ParseIntError; + + type Result = std::result::Result; + + #[derive(Debug)] + enum DoubleError { + EmptyVec, + // 自定义一个错误类型,内部封装了 `ParseIntError` 错误 + Parse(ParseIntError), + } + + impl fmt::Display for DoubleError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + DoubleError::EmptyVec => write!(f, "please use a vector with at least one element"), + // 实现包装错误的特殊信息提示 + DoubleError::Parse(..) => { + write!(f, "the provided string could not be parsed as int") + } + } + } + } + + impl error::Error for DoubleError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match *self { + DoubleError::EmptyVec => None, + // The cause is the underlying implementation error type. Is implicitly + // cast to the trait object `&error::Error`. This works because the + // underlying type already implements the `Error` trait. + DoubleError::Parse(ref e) => Some(e), + } + } + } + + // 实现从 `ParseIntError` 类型转换到自定义的 `DoubleError` 类型。 + // 当 `?` 操作符需要转换 `ParseIntError` 到 `DoubleError` 的时候会自动调用该函数 + impl From for DoubleError { + fn from(err: ParseIntError) -> DoubleError { + DoubleError::Parse(err) + } + } + + fn double_first(vec: Vec<&str>) -> Result { + let first = vec.first().ok_or(DoubleError::EmptyVec)?; + // 这里如果出现错误 `?` 会自动调用上面实现的转换方法。 + let parsed = first.parse::()?; + + Ok(2 * parsed) + } + + fn print(result: Result) { + match result { + Ok(n) => println!("The first doubled is {}", n), + Err(e) => { + println!("Error: {}", e); + if let Some(source) = e.source() { + println!(" Caused by: {}", source); + } + } + } + } + + let numbers = vec!["42", "93", "18"]; + let empty = vec![]; + let strings = vec!["tofu", "93", "18"]; + + print(double_first(numbers)); + print(double_first(empty)); + print(double_first(strings)); +} + +/// +/// 在迭代器中使用 `Result` +/// +fn iterating_over_results() { + // `Iter::map` 的闭包返回值可能是错误 + { + let strings = vec!["tofu", "93", "18"]; + let numbers: Vec<_> = strings.into_iter().map(|s| s.parse::()).collect(); + println!("Results: {:?}", numbers); + } + // 使用 `Iter::filter_map` 来过滤掉失败的结果 + { + let strings = vec!["tofu", "93", "18"]; + let numbers: Vec<_> = strings + .into_iter() + .filter_map(|s| s.parse::().ok()) + .collect(); + println!("Results: {:?}", numbers); + } + + // 使用 `map_err` 和 `filter_map` 来分别保存成功和失败 + { + let strings = vec!["42", "tofu", "93", "999", "18"]; + let mut errors = vec![]; + let numbers: Vec<_> = strings + .into_iter() + .map(|s| s.parse::()) + .filter_map(|r| r.map_err(|e| errors.push(e)).ok()) + .collect(); + println!("Numbers: {:?}", numbers); + println!("Errors: {:?}", errors); + } + + // 如果发现有失败的情况则直接终止转换,并返回错误 + // 这里因为 `Result` 实现了 `FromIterator` 特性, + // 所以可以自动转换 `Vec>` 类型为 `Result, E>` + // 但是一旦转换过程中出现 `Result:Err` 则迭代器就会直接终止,不再尝试后续转换。 + { + let strings = vec!["tofu", "93", "18"]; + let numbers: Result, _> = strings.into_iter().map(|s| s.parse::()).collect(); + println!("Results: {:?}", numbers); + } + + // 使用 `partition` 方法来自动对数据进行分类。 + { + let strings = vec!["tofu", "93", "18"]; + let (numbers, errors): (Vec<_>, Vec<_>) = strings + .into_iter() + .map(|s| s.parse::()) + // partition 接收一个闭包,该闭包返回一个 `bool` 值,然后对两种返回值自动进行分类,并且返回分类好的元组 + .partition(Result::is_ok); + println!("Numbers: {:?}", numbers); + println!("Errors: {:?}", errors); + } + + // 经过上面的分类发现结果还是包装在了 `Result` 中, + // 想要去掉层包装只能再多写两行模版代码来去掉不同类型的包装 + { + let strings = vec!["tofu", "93", "18"]; + let (numbers, errors): (Vec<_>, Vec<_>) = strings + .into_iter() + .map(|s| s.parse::()) + .partition(Result::is_ok); + + // 使用 `Result::unwrap` 方法来去掉 `Ok` 的包装 + let numbers: Vec<_> = numbers.into_iter().map(Result::unwrap).collect(); + + // 使用 `Result::unwrap_err` 方法来去掉 `Err` 的包装 + let errors: Vec<_> = errors.into_iter().map(Result::unwrap_err).collect(); + println!("Numbers: {:?}", numbers); + println!("Errors: {:?}", errors); + } +} + +fn main() { + // 明确调用 `panic!` 主动退出。 + // panic_(); + + // 使用宏来定义不同的错误行为 + // abort_and_unwind(); + + // 使用 `Option` 来处理错误。 + // option_and_unwrap(); + + // `Option` 相关的使用方法 + { + // 使用 `?` 操作符来快捷获取数据。 + unpacking_options_with_question_mark(); + + // `map` 方法只处理有值的情况,并对闭包返回值进行 `Option` 包装。 + combinators_map(); + + // `and_then` 方法只处理有值的情况,不对返回值进行包装,但是要求闭包必须返回一个 `Option` 的包装值。 + combinators_and_then(); + + // 常用的其他方法 `or` `or_else` `get_or_insert` `get_or_insert_with` + unpacking_options_and_default(); + } + + // `Result` 相关的使用方法 + { + // `Result` 对象的基础介绍 + result(); + // 使用 `Result` 定义 `main` 函数的返回值 + using_result_in_main(); + // `map` 方法和 `Option` 中的方法一致 + map_for_result(); + // 为 `Result` 定义类型别名 + aliases_for_result(); + // 提前返回 + eraly_result(); + // `?` 操作符简化代码逻辑,以及操作符的前身 `try!` 宏 + intorducing_question_mark(); + } + + // 同时处理多种错误类型 + { + // 基础示例 + multiple_error_types(); + // `Result` 和 `Option` 类型的互相嵌套使用 + pulling_results_out_of_options(); + // 自定义错误类型 + defining_an_error_type(); + // 错误类型的装箱,实现多种错误类型并存 + boxing_errors(); + // 使用 `?` 优化错误转换代码 + other_uses_of_question_mark(); + // 完整的自定义错误类型示例 + wrapping_errors(); + } + + // 迭代器中使用 `Result` + iterating_over_results(); +} diff --git a/19.Std Library types/Cargo.lock b/19.Std Library types/Cargo.lock new file mode 100644 index 0000000..9fdc596 --- /dev/null +++ b/19.Std Library types/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "std_library_types" +version = "0.1.0" diff --git a/19.Std Library types/Cargo.toml b/19.Std Library types/Cargo.toml new file mode 100644 index 0000000..85fdd6c --- /dev/null +++ b/19.Std Library types/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "std_library_types" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/19.Std Library types/src/main.rs b/19.Std Library types/src/main.rs new file mode 100644 index 0000000..407ec0d --- /dev/null +++ b/19.Std Library types/src/main.rs @@ -0,0 +1,798 @@ +//! +//! 标准库 +//! +//! Rust 标准库提供了很多基于原始类型的扩展类型,比较常用的是下面这些 +//! +//! - 可变长的 `String` 类型: `"hello world"` +//! - 可变长的 `Vec` 类型:`[1,2,3]` +//! - 可选的 `Option` 类型: `Option` +//! - 用于错误处理的 `Result` 类型:`Result` +//! - 基于堆内存动态分配的指针指针 `Box` 类型:`Box` +//! + +/// +/// 堆内存动态分配的智能指针 `Box` +/// +/// 在 `Rust` 中数据默认都会保存在栈(`stack`)上,如果一个值被 `Box` 包装,则该数据 +/// 会自动保存在堆上,`Box` 可以存放任意的类型,当 `Box` 走出作用域则相应的堆内存也会被回收。 +/// +/// 被装箱的值可以使用 `*` 解引用操作符来移除掉一层指针的引用。 +/// +fn box_() { + use std::mem; + + #[allow(dead_code)] + #[derive(Debug, Clone, Copy)] + struct Point { + x: f64, + y: f64, + } + + #[allow(dead_code)] + struct Rectangle { + top_left: Point, + bottom_right: Point, + } + + fn origin() -> Point { + Point { x: 0.0, y: 0.0 } + } + + fn boxed_origin() -> Box { + // 在堆上申请一块内存来保存 `Point` 实例,并且使用 `Box` 进行装箱,并返回 `Box` + Box::new(Point { x: 0.0, y: 0.0 }) + } + + // 这里的类型标注都可以直接去掉,因为编译器能自动推断出类型 + // 这两个类型都是储存在栈上的 + let point: Point = origin(); + let rectangle: Rectangle = Rectangle { + top_left: origin(), + bottom_right: Point { x: 3.0, y: -4.0 }, + }; + + // 这个是储存在堆上的 + let boxed_rectangle: Box = Box::new(Rectangle { + top_left: origin(), + bottom_right: Point { x: 3.0, y: -4.0 }, + }); + + // 这个也是储存在堆上的 + let boxed_point: Box = Box::new(origin()); + + // 双重 `Box` 装箱 + let box_in_a_box: Box> = Box::new(boxed_origin()); + + println!( + "Point occupies {} bytes on the stack", + mem::size_of_val(&point) + ); + println!( + "Rectangle occupies {} bytes on the stack", + mem::size_of_val(&rectangle) + ); + + // `Box` 占用的内存空间等于指针占用的内存空间 + println!( + "Boxed point occupies {} bytes on the stack", + mem::size_of_val(&boxed_point) + ); + println!( + "Boxed rectangle occupies {} bytes on the stack", + mem::size_of_val(&boxed_rectangle) + ); + println!( + "Boxed box occupies {} bytes on the stack", + mem::size_of_val(&box_in_a_box) + ); + + // 从堆上复制数据到栈上,这里是复制不是转移所有权是因为 `Point` 实现了 `Copy` 和 `Clone` + let unboxed_point: Point = *boxed_point; + + println!( + "Unboxed point occupies {} bytes on the stack", + mem::size_of_val(&unboxed_point) + ); +} + +/// +/// 可变数组(vectors) +/// +/// 可变数组和切片很相似,在编译阶段都不知道具体的长度是多少, +/// 而且可以在运行的过程中增加或减小长度,一个可变数组拥有三个关键的值 +/// +/// - 数据的指针 +/// - 当前数组的长度 (就是当前有效数据的实际长度) +/// - 当前数组的容量 (是当前数组从堆内存中分配的长度,该长度有一定冗余,会根据有效数据的长度自动扩容或者缩容) +/// +fn vectors() { + // 迭代器可以转换成一个可变数组 + let collected_iterator: Vec = (0..10).collect(); + println!("Collected (0..10) into: {:?}", collected_iterator); + + // `vec!` 宏可以初始化一个任意长度数组 + let mut xs = vec![1i32, 2, 3]; + println!("Initial vector: {:?}", xs); + + // 向数组中添加数据 + println!("Push 4 into the vector"); + xs.push(4); + println!("Vector: {:?}", xs); + + // 错误: 不可变数组不能添加数据 + // collected_iterator.push(0); + // TODO ^ 移除注释查看错误 + + // `len` 方法可以告诉你当前的数组长度是多少 + println!("Vector length: {}", xs.len()); + + // 数组可以通过方括号访问数据,索引值从 `0` 开始 + println!("Second element: {}", xs[1]); + + // `pop` 方法会返回并移除掉数组的最后一个元素 + println!("Pop last element: {:?}", xs.pop()); + + // 如果访问的索引超过数组的有效数据数量则会崩溃 + // println!("Fourth element: {}", xs[3]); + // TODO ^ 移除注释查看错误 + + // 数组合一使用 `iter` 方法转换成迭代器使用 `for` 来迭代访问数组中的数据 + println!("Contents of xs:"); + for x in xs.iter() { + println!("> {}", x); + } + + // 数组还可以利用迭代器的 `enumerate` 方法来对数据进行计数 + for (i, x) in xs.iter().enumerate() { + println!("In position {} we have value {}", i, x); + } + + // 还可以使用 `iter_mut` 方法来生成可变引用的迭代器,这样就可以在迭代的过程中修改数组的内容。 + for x in xs.iter_mut() { + *x *= 3; + } + println!("Updated vector: {:?}", xs); +} + +/// +/// 在 `Rust` 中有两种字符串 `String` 和 `str`。 +/// +/// `String` 内部使用的是 `Vec` 类型保存字符串的每一个字节, +/// 同时保证了字符串一定是一个有效的 `UTF-8` 格式的序列, +/// `String` 因为使用了 `Vec` 所以数据是保存在堆中的是可变长的,并且不是以 `null` 为终止的。 +/// +/// `&str` 像是一个切片类似于 `&[u8]` 并且指向的内存一定是有效的 `UTF-8` 格式的序列, +/// 就像是对于 `String` 内部数据的引用,就像是 `&[T]` 是对 `Vec` 的引用一样。 +/// +fn strings() { + { + // 所有的类型声明都是可以删除的,因为编译器能推断出类型是什么。 + // 所有的字面量都是对于程序中一段只读内存的引用。 + let pangram: &'static str = "the quick brown fox jumps over the lazy dog"; + println!("Pangram: {}", pangram); + + // 把字符串转换成迭代器并且翻转字符串,这里并没有生成新的字符还只是对原始内存的引用。 + println!("Words in reverse"); + for word in pangram.split_whitespace().rev() { + println!("> {}", word); + } + + // `collect` 会对数据进行复制并生成一份新的数据。 + let mut chars: Vec = pangram.chars().collect(); + chars.sort(); // 对数据进行排序 + chars.dedup(); // 对数据进行去重 + + // 创建一个空的 `String` 类型 + let mut string = String::new(); + for c in chars { + // 向字符串中添加单个字符 + string.push(c); + // 向字符串中添加一段字符串 + string.push_str(", "); + } + + // 对固定长度的 `[str, 2]` 数组创建一个不可变切片。 + let chars_to_trim: &[char] = &[' ', ',']; + // 对字符串数组进行 `trim_matches` 删除指定的首位字符,这里实际上只是对原始的字符串重新创建了一个引用的切片 + // 并没有生成任何新的字符串。 + let trimmed_str: &str = string.trim_matches(chars_to_trim); + println!("Used characters: {}", trimmed_str); + + // 申请一块内存来存放字符串 + let alice = String::from("I like dogs"); + // 申请一块新的内存来存放变更后的字符串 + let bob: String = alice.replace("dog", "cat"); + + println!("Alice says: {}", alice); + println!("Bob says: {}", bob); + } + + // 字面量和转义字符 + { + // 你可以使用转义字符直接书写十六进制的字节码 + let byte_escape = "I'm writing \x52\x75\x73\x74!"; + println!("What are you doing\x3F (\\x3F means ?) {}", byte_escape); + + // 还可以写 `Unicode Code` 编码。 + let unicode_codepoint = "\u{211D}"; + let character_name = "\"DOUBLE-STRUCK CAPITAL R\""; + + println!( + "Unicode character {} (U+211D) is called {}", + unicode_codepoint, character_name + ); + + // 字符串还可以是多行的 + let long_string = "String literals + can span multiple lines. + The linebreak and indentation here ->\ + <- can be escaped too!"; + println!("{}", long_string); + } + + // 有时候在字符串中因为有太多的字符需要被转移所以提供了 `raw string` 的简便写法, + // 所有在 `raw string` 中的字符串都不会被转义。 + { + // 转义字符 `\` 在这个字符串中不生效 + let raw_str = r"Escapes don't work here: \x3F \u{211D}"; + println!("{}", raw_str); + + // 如果你的字符中还包含了 `"` 符号的话,还可以使用 `#` 来包围字符串 + let quotes = r#"And then I said: "There is no escape!""#; + println!("{}", quotes); + + // 如果你还需要 `#` 在你的字符串中出现,则还可以使用 `#s` 语法,其中 `s` 代表了重复书写 `#` 多少次, + // 只要前后使用的是相同数量的标记则就认为是有效的字符串,你最多可以使用 65536 个 `#` 符号。 + let longer_delimiter = r###"A string with "# in it. And even "##!"###; + println!("{}", longer_delimiter); + } + + // 如果你使用的字符串格式不是 `UTF-8` 的 (`String` 和 `str` 都只能存放 `UTF-8`),那你可以使用 `字节字符串(Byte strings)`。 + { + use std::str; + // Note that this is not actually a `&str` + // 这个字面量并不是 `&str` 只是一个字节序列。 + let bytestring: &[u8; 21] = b"this is a byte string"; + + // 字节数组并没有实现 `Display` 特性,所以打印的是原始的字节的数字。 + println!("A byte string: {:?}", bytestring); + + // 字节字符串可以使用转义字符来直接书写十六进制编码 + let escaped = b"\x52\x75\x73\x74 as bytes"; + // 但是不能使用 `Unicode Code` 编码 + // let escaped = b"\u{211D} is not allowed"; + println!("Some escaped bytes: {:?}", escaped); + + // `raw byte strings` 和 `raw strings` 一样 + let raw_bytestring = br"\u{211D} is not escaped here"; + println!("{:?}", raw_bytestring); + + // 转换字节数组到 `str` 类型,可能会失败因为字节数组的数据可能不是有效的 `UTF-8` 字符串 + if let Ok(my_str) = str::from_utf8(raw_bytestring) { + println!("And the same as text: '{}'", my_str); + } + + // 这里也是一样的效果 + let _quotes = br#"You can also use "fancier" formatting, \ + like with normal raw strings"#; + + // 定义一个不是 `UTF-8` 格式的字节数组 + let shift_jis = b"\x82\xe6\x82\xa8\x82\xb1\x82\xbb"; // "ようこそ" in SHIFT-JIS + + // 这里会转换失败,因为上面的字节数组不是有效的 `UTF-8` 格式的 + match str::from_utf8(shift_jis) { + Ok(my_str) => println!("Conversion successful: '{}'", my_str), + Err(e) => println!("Conversion failed: {:?}", e), + }; + } + // 更多编码转换相关的可以[查看这里](https://crates.io/crates/encoding) + // 更多关于字符串字面量和转义字符的相关详情可以[查看这里](https://doc.rust-lang.org/reference/tokens.html) +} + +/// +/// 很多时候我们更希望可以捕获到错误,而不是出现错误就让程序崩溃, +/// 可以通过使用 `Option` 来实现这个效果,`Option` 本身是一个枚举包含如下值 +/// +/// - None,表示失败或者没有值 +/// - Some(value),一个元组的结构,有一个 `value` 值可以是任意类型的 +/// +fn option() { + // 数字除 `0` 返回一个 `None`,否则返回除法结果。 + fn checked_division(dividend: i32, divisor: i32) -> Option { + if divisor == 0 { + None + } else { + Some(dividend / divisor) + } + } + + fn try_division(dividend: i32, divisor: i32) { + // 通过 `match` 来处理 `Option` 的结果,执行特定代码。 + match checked_division(dividend, divisor) { + None => println!("{} / {} failed!", dividend, divisor), + Some(quotient) => { + println!("{} / {} = {}", dividend, divisor, quotient) + } + } + } + + try_division(4, 2); + try_division(1, 0); + + // 绑定 `None` 到变量上,需要手动声明 `Option` 的类型 + let none: Option = None; + // 或者使用这个语法快捷绑定 + let _equivalent_none = None::; + + // 这里会自动推断类型 + let optional_float = Some(0f32); + + // 使用 `unwrap` 方法获取内部数据 + println!( + "{:?} unwraps to {:?}", + optional_float, + optional_float.unwrap() + ); + + // 如果数据为 `None` 的话 `unwrap` 会调用 `panic!` + // println!("{:?} unwraps to {:?}", none, none.unwrap()); + // TODO: 移除注释后运行查看错误 +} + +/// +/// 我们看到了 `Option` 枚举可以用作返回值可以表示操作可能失败,但是 `None` 没有办法携带 +/// 具体的失败原因,有时候我们需要明确的错误原因来告知用于为什么出现了错误,所以可以使用 `Result` 枚举。 +/// +/// `Result` 拥有两个成员 +/// +/// - Ok(value) 表示当前操作成功了,并且 `value` 是结果的值,`value` 是 `T` 类型的。 +/// - Err(why) 表示当前的操作失败了,并且失败原因保存在了 `why` 中,`why` 是 `E` 类型的。 +/// +fn result() { + mod checked { + // 自定义错误 + #[derive(Debug)] + pub enum MathError { + DivisionByZero, + NonPositiveLogarithm, + NegativeSquareRoot, + } + + pub type MathResult = Result; + + pub fn div(x: f64, y: f64) -> MathResult { + if y == 0.0 { + // 这里分母为 `0` 是不允许的所以抛出错误 + Err(MathError::DivisionByZero) + } else { + // 这里正常 + Ok(x / y) + } + } + + pub fn sqrt(x: f64) -> MathResult { + if x < 0.0 { + Err(MathError::NegativeSquareRoot) + } else { + Ok(x.sqrt()) + } + } + + pub fn ln(x: f64) -> MathResult { + if x <= 0.0 { + Err(MathError::NonPositiveLogarithm) + } else { + Ok(x.ln()) + } + } + } + + // `op(x, y)` === `sqrt(ln(x / y))` + fn op(x: f64, y: f64) -> f64 { + // 这是一个三层的 `match` 金字塔 + match checked::div(x, y) { + Err(why) => panic!("{:?}", why), + Ok(ratio) => match checked::ln(ratio) { + Err(why) => panic!("{:?}", why), + Ok(ln) => match checked::sqrt(ln) { + Err(why) => panic!("{:?}", why), + Ok(sqrt) => sqrt, + }, + }, + } + } + + // 这里会失败吗? + // println!("{}", op(1.0, 10.0)); +} + +/// +/// 当使用链条式的 `match` 来写代码会降低代码的可读性而且很混乱, +/// 幸运的使我们可以使用 `?` 操作符来简化这个过程,让代码重新变得可读。 +/// +/// `?` 操作符可以检查表达式返回的 Result 的值是否是 Err(T) 如果是的话 +/// 会自动直接返回错误值,等于 `return Err(From::from(err))`,反之 +/// 继续执行剩下的代码逻辑。 +/// +fn result_and_question_mark() { + mod checked { + #[derive(Debug)] + enum MathError { + DivisionByZero, + NonPositiveLogarithm, + NegativeSquareRoot, + } + + type MathResult = Result; + + fn div(x: f64, y: f64) -> MathResult { + if y == 0.0 { + Err(MathError::DivisionByZero) + } else { + Ok(x / y) + } + } + + fn sqrt(x: f64) -> MathResult { + if x < 0.0 { + Err(MathError::NegativeSquareRoot) + } else { + Ok(x.sqrt()) + } + } + + fn ln(x: f64) -> MathResult { + if x <= 0.0 { + Err(MathError::NonPositiveLogarithm) + } else { + Ok(x.ln()) + } + } + + // 这里使用 `?` 来优化代码逻辑 + fn op_(x: f64, y: f64) -> MathResult { + // 如果 `div` 返回失败,则直接返回失败的结果。 + let ratio = div(x, y)?; + + // 如果 `ln` 返回失败, 则直接返回失败的结果。 + let ln = ln(ratio)?; + + sqrt(ln) + } + + pub fn op(x: f64, y: f64) { + match op_(x, y) { + Err(why) => panic!( + "{}", + match why { + MathError::NonPositiveLogarithm => "logarithm of non-positive number", + MathError::DivisionByZero => "division by zero", + MathError::NegativeSquareRoot => "square root of negative number", + } + ), + Ok(value) => println!("{}", value), + } + } + } + + // checked::op(1.0, 10.0); +} + +/// +/// `panic!` 宏可以让当前线程退出并且展开调用堆栈,在此期间会把当前线程所拥有的所有对象都进行回收(会调用所有对象的析构函数 `drop`), +/// 当程序只有一个线程的时候,`panic!` 会打印当前的调用栈和错误信息,同时退出进程。 +/// +fn panic_() { + // 重新实现除法 + fn division(dividend: i32, divisor: i32) -> i32 { + if divisor == 0 { + // 这里直接调用 `panic!` 宏终止进程并提示错误信息 + panic!("division by zero"); + } else { + dividend / divisor + } + } + + division(3, 0); + + println!("This point won't be reached!"); +} + +/// +/// 哈希表 (HashMap) +/// +/// 数组和动态数组都使用整数来存取数据,`HashMap` 使用 `key` 进行存取数据, +/// `key` 可以是 `booleans(布尔值)`,`integers(整数)`,`strings(字符串)`, +/// 或者任意类型只要该类型实现了 `Eq` 和 `Hash` 特性的都可以。 +/// +/// `HashMap` 是一个可以动态扩容和缩容的容器,也可以手动通过 `HashMap::with_capcity` 指定一个初始容量的 `HashMap` +/// 或者使用 `HashMap::new()` 来创建一个空的 `HashMap`。 (更推荐创建空的哈希表) +/// +fn hash_map() { + use std::collections::HashMap; + + fn call(number: &str) -> &str { + match number { + "798-1364" => { + "We're sorry, the call cannot be completed as dialed. + Please hang up and try again." + } + "645-7689" => { + "Hello, this is Mr. Awesome's Pizza. My name is Fred. + What can I get for you today?" + } + _ => "Hi! Who is this again?", + } + } + + let mut contacts = HashMap::new(); + + contacts.insert("Daniel", "798-1364"); + contacts.insert("Ashley", "645-7689"); + contacts.insert("Katie", "435-8291"); + contacts.insert("Robert", "956-1745"); + + // 通过 `key` 获取数据的引用 + match contacts.get(&"Daniel") { + Some(&number) => println!("Calling Daniel: {}", call(number)), + _ => println!("Don't have Daniel's number."), + } + + // `HashMap::insert()` 当 `key` 不存在的时候返回 `None`, + // 否则返回被更新的值。 + contacts.insert("Daniel", "164-6743"); + + match contacts.get(&"Ashley") { + Some(&number) => println!("Calling Ashley: {}", call(number)), + _ => println!("Don't have Ashley's number."), + } + + contacts.remove(&"Ashley"); + + // `HashMap::iter()` 可以创建一个迭代器,这个迭代器可以访问哈希表中的所有数据。 + for (contact, &number) in contacts.iter() { + println!("Calling {}: {}", contact, call(number)); + } +} + +/// +/// 哈希表自定义 `key` 类型 +/// +/// 一个类型只要实现了 `Eq` `Hash` 这两个特性就可以当做哈希表的 `key` 来使用, +/// Rust 内置可以直接当做 key 使用的类型如下 +/// +/// - `bool` 不常用因为只有两种可能。 +/// - `int`,`uint` 所有整数的子类都可以使用,注意不包含 `f32` 和 `f64` 因为浮点数的精度问题会导致计算散列的时候出错。 +/// - `String`,`&str` 字符串 +/// +/// 如果一个类型实现了 `Eq` 和 `Hash` 那么对应他的容器也会实现这两个方法,比如说 +/// `Vec` 这里 `T` 实现了 `Eq` 和 `Hash` 则 `Vec` 也会自动实现 `Hash` 和 `Eq` +/// +/// 可以通过属性宏 `#[derive(PartialEq, Eq, Hash)]` 非常简单的就能实现 `Eq` 和 `Hash` 特性, +/// 剩下的会由编译器自动实现相关的具体代码。 +/// +/// 下面这个示例只是演示怎么在哈希表中使用自定义类型来当做 `key` 而不是具体的怎么实现 `Hash` 特性。 +/// +fn alternate_custom_key_types() { + use std::collections::HashMap; + + // `Eq` 要求必须实现 `PartialEq`。 + #[derive(PartialEq, Eq, Hash)] + struct Account<'a> { + username: &'a str, + password: &'a str, + } + + struct AccountInfo<'a> { + name: &'a str, + email: &'a str, + } + + // 定义类型别名,使用自定义结构当做哈希表的 `key` + type Accounts<'a> = HashMap, AccountInfo<'a>>; + + fn try_logon<'a>(accounts: &Accounts<'a>, username: &'a str, password: &'a str) { + println!("Username: {}", username); + println!("Password: {}", password); + println!("Attempting logon..."); + + let logon = Account { username, password }; + + match accounts.get(&logon) { + Some(account_info) => { + println!("Successful logon!"); + println!("Name: {}", account_info.name); + println!("Email: {}", account_info.email); + } + _ => println!("Login failed!"), + } + } + + let mut accounts: Accounts = HashMap::new(); + + let account = Account { + username: "j.everyman", + password: "password123", + }; + + let account_info = AccountInfo { + name: "John Everyman", + email: "j.everyman@email.com", + }; + + // 把结构和内容进行关联 + accounts.insert(account, account_info); + + try_logon(&accounts, "j.everyman", "psasword123"); + + try_logon(&accounts, "j.everyman", "password123"); +} + +/// +/// 哈希集合(HashSet) +/// +/// 可以把 `HashSet` 当做一个没有 `value` 的 `HashMap`,(底层也确实通过 `HashMap` 实现的) +/// 你可能很好奇这么做的原因是为了什么,毕竟已经有了动态数组 (`Vec`)。 +/// 这里最重要的原因是 `HashSet` 有一个特殊的特性就是可以保证容器内不包含重复的元素,但是 `Vec` 做不到这一点。 +/// 如果你尝试在 `HashSet` 中重复的设置一个值(新老值拥有同一个 `Hash` 值),那么这个新的值会直接替换掉老的值。 +/// +/// HashSet 还拥有四种集合的基本运算。 +/// +/// - `union` 并集 `A` 和 `B` 共同拥有的集合 +/// - `defference` 差集 `A` 和 `B` 不想交的 `A` 的集合 +/// - `intersection` 交集 `A` 和 `B` 共同拥有的集合 +/// - `symmetric_difference` 对称差集 `A` 和 `B` 的并集中排除掉 `A` 和 `B` 交集的集合 +/// +/// +fn hash_set() { + use std::collections::HashSet; + + let mut a: HashSet = vec![1i32, 2, 3].into_iter().collect(); + let mut b: HashSet = vec![2i32, 3, 4].into_iter().collect(); + + assert!(a.insert(4)); + assert!(a.contains(&4)); + + // 因为 `b` 中已经存在 `4` 了 所以 `HashSet::insert()` 返回 `false` + // assert!(b.insert(4), "Value 4 is already in set B!"); + // TODO ^ 移除注释查看错误 + + b.insert(5); + + // 如果容器内的类型实现了 `Debug` 则容器会自动实现 `Debug` + println!("A: {:?}", a); + println!("B: {:?}", b); + + // `a` 和 `b` 的 并集 `[1, 2, 3, 4, 5]` 但是不保证顺序 + println!("Union: {:?}", a.union(&b).collect::>()); + + // `a` 和 `b` 的 差集 `[1]` 但是不保证顺序 + println!("Difference: {:?}", a.difference(&b).collect::>()); + + // `a` 和 `b` 的交集 `[2, 3, 4]` 但是不保证顺序 + println!( + "Intersection: {:?}", + a.intersection(&b).collect::>() + ); + + // `a` 和 `b` 的对称差集 `[1, 5]` 但是不保证顺序 + println!( + "Symmetric Difference: {:?}", + a.symmetric_difference(&b).collect::>() + ); +} + +/// +/// `Rc` (引用计数 `Reference Counting`) +/// +/// 让一个资源拥有多个拥有者,`Rc` 对象可以自动跟踪资源的引用计数,当引用技术归零则会自动销毁被引用的资源。 +/// 当一个 `Rc` 资源被克隆一份则对象的引用计数就会增加1,如果被克隆的对象被销毁则引用计数自动减1,直到引用 +/// 计数归零,则会自动销毁对应的资源。 +/// +/// 对 `Rc` 对象进行克隆,并不会深度拷贝数据,只是复制了一个新的 `Rc` 对象这个对象指向原始数据,并且增加对原始对象的引用计数。 +/// +fn rc_() { + use std::rc::Rc; + let rc_examples = "Rc examples".to_string(); + { + println!("--- rc_a is created ---"); + + // `Rc::new` 方法会转移参数的所有权 + let rc_a: Rc = Rc::new(rc_examples); + // 查询引用计数,这里会返回 1 + println!("Reference Count of rc_a: {}", Rc::strong_count(&rc_a)); + + { + println!("--- rc_a is cloned to rc_b ---"); + + // 通过 `Rc::clone` 方法复制一个新的引用对象,原始的引用计数 `+1` + let rc_b: Rc = Rc::clone(&rc_a); + + // 因为 `rc_a` 和 `rc_b` 是对同一个对象的引用使用的是同一个引用计数器,所以这里 `rc_b` 是 2, `rc_a` 也是 2 + println!("Reference Count of rc_b: {}", Rc::strong_count(&rc_b)); + println!("Reference Count of rc_a: {}", Rc::strong_count(&rc_a)); + + // 这两个引用都是对同一个资源的 所以这两个资源是同一个 + println!("rc_a and rc_b are equal: {}", rc_a.eq(&rc_b)); + + // 因为 `Rc` 实现了 `Deref` 特性,所以这里访问的方法实际上是内部对象 `String` 的方法 + println!("Length of the value inside rc_a: {}", rc_a.len()); + println!("Value of rc_b: {}", rc_b); + + println!("--- rc_b is dropped out of scope ---"); + } + + // 这里 `rc_a` 是 1,因为 `rc_b` 已经因为作用域销毁被自动回收了,所以引用计数被 `-1` + println!("Reference Count of rc_a: {}", Rc::strong_count(&rc_a)); + + println!("--- rc_a is dropped out of scope ---"); + } + + // 错误!`rc_examples` 因为已经被前一个作用域转移了所有权,所以这里不能再使用这个变量了。 + // println!("rc_examples: {}", rc_examples); + // TODO ^ 移除注释查看错误 +} + +/// +/// Arc (Atomically Reference Counted)原子性引用计数器,多线程版本的引用计数器 +/// 其引用计数的规则和 `Rc` 是一模一样的,只不过内部针对线程实现了一个线程之间的数据安全转移的特性。 +/// 这个引用计数器专门为了多线程设计的,可以保证数据再多个线程之间安全转移。 +/// +fn arc() { + use std::sync::Arc; + use std::thread; + use std::time::Duration; + + // 创建一个线程安全的数据引用 + let apple = Arc::new("the same apple"); + + for _ in 0..10 { + // 通过 `clone` 方法给线程创建一个不可变引用,这个引用可以直接传递给线程。 + let apple = Arc::clone(&apple); + + thread::spawn(move || { + // 因为闭包声明的时候会自动捕获当前作用域下的外部变量使用 `move` 关键字让变量直接转移到线程中。 + println!("{:?}", apple); + }); // 当线程执行完毕以后会自动回收掉所有引用的变量 这里会自动回收掉 `apple` 这个变量。 + } + + // 让当前线程等待1秒钟,好让所有的线程有时间能执行完内部的代码逻辑。 + thread::sleep(Duration::from_secs(1)); +} + +fn main() { + // `Box` 堆内存动态分配的智能指针 + box_(); + + // `Vec` 可变数组 + vectors(); + + // `String` 可变字符串 + strings(); + + // `Option` 枚举 + option(); + + // `Result` 枚举 + result(); + + // `Result` 和 `?` 表达式。 + result_and_question_mark(); + + // 主动触发异常 + // panic_(); + + // `HashMap` 哈希表 + hash_map(); + // 自定义哈希表 `key` 类型 + alternate_custom_key_types(); + + // `HashSet` 集合 + hash_set(); + + // `Rc` 引用计数器 + rc_(); + + // `Arc` 多线程引用计数器 + arc(); +}