Rust学习 | Rustlings通关记录与题解

打印 上一主题 下一主题

主题 869|帖子 869|积分 2607

2023年6月19日决定对rust做一个重新的梳理,整理今年4月份做完的rustlings,根据自己的理解来写一份题解,记录在此。
周折很久,因为中途经历了推免的各种麻烦事,以及选择数据库作为未来研究方向后的一段适应过程,耽搁了很久。
2023年10月份秋冬季的开源操作系统训练营又开始了,所以我回来继续整理。继续进行我的os大业。
rustlings 版本众多,往往几个月内可能就不完全一致,本份题解基于rustlings 5.5.1,rustlings list显示95道题。梳理完成于2023年10月22日。Github仓库


0. 熟悉rustlings

rustlings是一个rustOJ形式的学习平台,通过90多道题目来测试rust语法的掌握程度,第一次接触的时候会感觉非常新颖,通过rustlings进行学习也非常高效。
1. intro


  • intro1:体验rustlings的用法,去除 //I AM NOT DONE即可。
  • intro2:体验rust的hello world,它的字符输出比python严格,比C简单。
2. variables


  • variables1:变量定义用let,其实在rust里 是一个数据绑定到一个变量,这是这个语言内存管理方面的创新点。
  • variables2:变量出现必须与一个数据做绑定,而绑定后默认不可修改,如果后续要对他修改,那就加上mut。
    这里还涉及一个if判断逻辑,类似于python2,不需要括号。
  • variables3:报错信息是变量x没有被初始化,所以变量出现必须与一个数据做绑定,同时i32是可以取掉的,因为rust编译器将自动将x推导为等式右边数字的类型。
  • variables4:后续数据修改,则第一次出现的数据定义(初始化)需要加上mut。
  • variables5:这里算是一个语法糖,当let过的变量再次被let,其实就是变量名的再利用,原来的变量名对应的数据将消失。
    第二个 number=3 修改为 let number = 3。
  • variables6:考察类似C语言中的全局变量的使用,const + 变量名(rust编译器强制要求全局变量名大写,是的,rust编译器会强制规定这种语言风格)+类型+初始值。
    同时,const定义的变量不能是mut,也就是不可修改。这也是出于并发操作安全的考虑。
3. functions


  • functions1:补一个函数即可,注意这里不要求被调用函数必须在调用者之前声明或者定义。
  • functions2:函数参数,在调用函数的括号内必须指定参数类型。
  • functions3:调用者缺少参数,加一个就行。
  • functions4:rust中返回值的表达
  • functions5:一个语法糖,如果函数中的某个语句不以分号;结尾,那么就是将这个语句(其实是一个value节点)作为返回值来返回。
    哈哈学了编译原理什么都想往语法树上想。

4. if


  • if1:if语句的基本写法,外加rust函数返回的特性。
  • if2:if语句的基本写法,外加rust函数的返回特性。
    1. pub fn foo_if_fizz(fizzish: &str) -> &str {
    2.     if fizzish == "fizz" {
    3.         "foo"
    4.     } else if fizzish == "fuzz"{
    5.         "bar"
    6.     } else {
    7.         "baz"
    8.     }
    9. }
    复制代码
5. quiz1

还有测试是我没想到的,quiz没有hint,那这样确实会比OJ体系更好玩一点。
写的if虽然很明显有问题,但测试用例就放在下面,所以这么写能通过测试。
  1. // Put your function here!
  2. fn calculate_price_of_apples(nums:i32) -> i32{
  3.     if nums <= 40{
  4.         nums*2
  5.     } else {
  6.         nums
  7.     }
  8. }
  9. // Don't modify this function!
  10. #[test]
  11. fn verify_test() {
  12.     let price1 = calculate_price_of_apples(35);
  13.     let price2 = calculate_price_of_apples(40);
  14.     let price3 = calculate_price_of_apples(41);
  15.     let price4 = calculate_price_of_apples(65);
  16.     assert_eq!(70, price1);
  17.     assert_eq!(80, price2);
  18.     assert_eq!(41, price3);
  19.     assert_eq!(65, price4);
  20. }
复制代码
</ul>11. strings


  • strings1:很明显,是函数的返回值类型(字符串字面量)和声明的返回值类型(String)不匹配. 注意,字符串字面量是切片类型&str。
    1. #[test]
    2. fn slice_out_of_array() {
    3.     let a = [1, 2, 3, 4, 5];
    4.     let nice_slice = &a[1..4];
    5.     assert_eq!([2, 3, 4], nice_slice)
    6. }
    复制代码
  • strings2:修改起来还是比较简单,形参实参类型保持一致。
    1. fn main() {
    2.     let cat = ("Furry McFurson", 3.5);
    3.     let /* your pattern here */ (name,age)= cat;
    4.     println!("{} is {} years old.", name, age);
    5. }
    复制代码
  • string3:考察字符串的相关操作,分别是去除空格的trim,切片->字符串的to_string()以及字符串连接"+",还有字符串替换repalce。
    1. #[test]
    2. fn indexing_tuple() {
    3.     let numbers = (1, 2, 3);
    4.     // Replace below ??? with the tuple indexing syntax.
    5.     let second = numbers.1;
    6.     assert_eq!(2, second,
    7.         "This is not the 2nd number in the tuple!")
    8. }
    复制代码
  • string4:进一步区分切片和字符串。
    1. fn array_and_vec() -> ([i32; 4], Vec<i32>) {
    2.     let a = [10, 20, 30, 40]; // a plain array
    3.     let v = vec![10, 20, 30, 40];// TODO: declare your vector here with the macro for vectors
    4.     (a, v)
    5. }
    6. #[cfg(test)]
    7. mod tests {
    8.     use super::*;
    9.     #[test]
    10.     fn test_array_and_vec_similarity() {
    11.         let (a, v) = array_and_vec();
    12.         assert_eq!(a, v[..]);
    13.     }
    14. }
    复制代码
12. modules

这部分就开始介绍rust的项目框架了。分为package、crate、mod三层。题目主要是在说mod。

  • moules1: 就是一个简单的访问权限控制,代码可见性
    1.   fn vec_loop(mut v: Vec<i32>) -> Vec<i32> {
    2.       for element in v.iter_mut() {
    3.           // TODO: Fill this up so that each element in the Vec `v` is
    4.           // multiplied by 2.
    5.           *element = *element*2;
    6.       }
    7.       // At this point, `v` should be equal to [4, 8, 12, 16, 20].
    8.       v
    9.   }
    10.   fn vec_map(v: &Vec<i32>) -> Vec<i32> {
    11.       v.iter().map(|element| {
    12.           // TODO: Do the same thing as above - but instead of mutating the
    13.           // Vec, you can just return the new number!
    14.           element*2
    15.       }).collect()
    16.   }
    17.   #[cfg(test)]
    18.   mod tests {
    19.       use super::*;
    20.       #[test]
    21.       fn test_vec_loop() {
    22.           let v: Vec<i32> = (1..).filter(|x| x % 2 == 0).take(5).collect();
    23.           let ans = vec_loop(v.clone());
    24.           assert_eq!(ans, v.iter().map(|x| x * 2).collect::<Vec<i32>>());
    25.       }
    26.       #[test]
    27.       fn test_vec_map() {
    28.           let v: Vec<i32> = (1..).filter(|x| x % 2 == 0).take(5).collect();
    29.           let ans = vec_map(&v);
    30.           assert_eq!(ans, v.iter().map(|x| x * 2).collect::<Vec<i32>>());
    31.       }
    32.   }
    复制代码
  • modules2:use关键字及其受限可见性,由于已知main函数中是 delicious_snacks::fruit,这样使用的,所以可知应该这样:
    1. fn main() {
    2.    let x = 1;
    3.    let sum = |y| x + y;
    4.     assert_eq!(3, sum(2));
    5. }
    复制代码
  • modules3:使用use导入标准库的包和成员,还有一个as别名。
    1. fn main() {
    2.     let mut x = 100;
    3.     let y = &mut x;
    4.     *y += 100;
    5.     let z = &mut x;
    6.     *z += 1000;
    7.     assert_eq!(x, 1200);
    8. }
    复制代码
说实话这几道题还是比较简单的,项目中可能还会比较复杂。圣经中还有一个受限可见性,更细粒度的控制可见性 pub(in crate a),还没认真理解。
13. hashmaps

HashMap 中存储的是一一映射的 KV 键值对,并提供了平均复杂度为 O(1) 的查询方法

  • hashmaps1:就考察HashMap的创建和更新。
    1. fn main() {
    2.     let  data = "Rust is great!".to_string();
    3.     get_char(data.clone());
    4.     string_uppercase(&data);
    5. }
    6. // Should not take ownership
    7. fn get_char(data: String) -> char {
    8.     data.chars().last().unwrap()
    9. }
    10. // Should take ownership
    11. fn string_uppercase(data: &String) {
    12.     let data = &data.to_uppercase();
    13.     println!("{}", data);
    14. }
    复制代码
  • hashmaps2:有多种实现(因为提供的函数确实很多)

      1. fn main() {
      2.     let data = "Rust is great!".to_string();
      3.     get_char(&data);// 不获取所有权,只有使用权
      4.     string_uppercase(data); // 获取所有权
      5. }
      6. // Should not take ownership
      7. fn get_char(data: &String) -> char {
      8.     data.chars().last().unwrap()
      9. }
      10. // Should take ownership
      11. fn string_uppercase(mut data: String) {
      12.     data = data.to_uppercase();
      13.     println!("{}", data);
      14. }
      复制代码
      1. struct ColorClassicStruct {
      2.     // TODO: Something goes here
      3.     red: u64,
      4.     green: u64,
      5.     blue: u64,
      6. }
      7. struct ColorTupleStruct(u64,u64,u64);
      8. #[derive(Debug)]
      9. struct UnitLikeStruct;
      10. #[cfg(test)]
      11. mod tests {
      12.     use super::*;
      13.     #[test]
      14.     fn classic_c_structs() {
      15.         // TODO: Instantiate a classic c struct!
      16.         let green = ColorClassicStruct {
      17.             red: 0,
      18.             green: 255,
      19.             blue: 0,
      20.         };
      21.         assert_eq!(green.red, 0);
      22.         assert_eq!(green.green, 255);
      23.         assert_eq!(green.blue, 0);
      24.     }
      25.     #[test]
      26.     fn tuple_structs() {
      27.         // TODO: Instantiate a tuple struct!
      28.         let green = (0,255,0);
      29.         assert_eq!(green.0, 0);
      30.         assert_eq!(green.1, 255);
      31.         assert_eq!(green.2, 0);
      32.     }
      33.     #[test]
      34.     fn unit_structs() {
      35.         // TODO: Instantiate a unit-like struct!
      36.         let unit_like_struct = UnitLikeStruct;
      37.         let message = format!("{:?}s are fun!", unit_like_struct);
      38.         assert_eq!(message, "UnitLikeStructs are fun!");
      39.     }
      40. }
      复制代码

  • hashmaps3:利用hashmaps2中的entry or_insert就可以解决,这里涉及到了这个函数的返回值:根据索引名找表项,如果没有则插入后再返回该表项。这就很容易解决hashmap3了。
    另外提一嘴,这里很多解答都会在struct Team中增加一个team成员,可以不增加的。
    1. let your_order = Order{
    2.             name: String::from("Hacker in Rust"),
    3.             count:1,
    4.             ..order_template
    5.         };
    复制代码
14. quiz2
  1. #[derive(Debug)]
  2. struct Package {
  3.     sender_country: String,
  4.     recipient_country: String,
  5.     weight_in_grams: i32,
  6. }
  7. impl Package {
  8.     fn new(sender_country: String, recipient_country: String, weight_in_grams: i32) -> Package {
  9.         if weight_in_grams <= 0 {
  10.             panic!("Can not ship a weightless package.")
  11.         } else {
  12.             Package {
  13.                 sender_country,
  14.                 recipient_country,
  15.                 weight_in_grams,
  16.             }
  17.         }
  18.     }
  19.     fn is_international(&self) -> bool {
  20.         // Something goes here...
  21.         return self.sender_country != self.recipient_country;
  22.     }
  23.     fn get_fees(&self, cents_per_gram: i32) ->  i32{
  24.         // Something goes here...
  25.         self.weight_in_grams * cents_per_gram
  26.     }
  27. }
  28. #[cfg(test)]
  29. mod tests {
  30.     use super::*;
  31.     #[test]
  32.     #[should_panic]
  33.     fn fail_creating_weightless_package() {
  34.         let sender_country = String::from("Spain");
  35.         let recipient_country = String::from("Austria");
  36.         Package::new(sender_country, recipient_country, -2210);
  37.     }
  38.     #[test]
  39.     fn create_international_package() {
  40.         let sender_country = String::from("Spain");
  41.         let recipient_country = String::from("Russia");
  42.         let package = Package::new(sender_country, recipient_country, 1200);
  43.         assert!(package.is_international());
  44.     }
  45.     #[test]
  46.     fn create_local_package() {
  47.         let sender_country = String::from("Canada");
  48.         let recipient_country = sender_country.clone();
  49.         let package = Package::new(sender_country, recipient_country, 1200);
  50.         assert!(!package.is_international());
  51.     }
  52.     #[test]
  53.     fn calculate_transport_fees() {
  54.         let sender_country = String::from("Spain");
  55.         let recipient_country = String::from("Spain");
  56.         let cents_per_gram = 3;
  57.         let package = Package::new(sender_country, recipient_country, 1500);
  58.         assert_eq!(package.get_fees(cents_per_gram), 4500);
  59.         assert_eq!(package.get_fees(cents_per_gram * 2), 9000);
  60.     }
  61. }
复制代码
尽在不言中。讨论to_string into to_owned,简要总结,用to_owned就好。
15. options

圣经中Options讲解

  • options1:Option如何构造和调用。
    1. #[derive(Debug)]
    2. enum Message {
    3.     // TODO: define a few types of messages as used below
    4.     Quit,
    5.     Echo,
    6.     Move,
    7.     ChangeColor,
    8. }
    9. fn main() {
    10.     println!("{:?}", Message::Quit);
    11.     println!("{:?}", Message::Echo);
    12.     println!("{:?}", Message::Move);
    13.     println!("{:?}", Message::ChangeColor);
    14. }
    复制代码
  • options2:调用 pop 方法时,它会从数组的末尾移除一个元素,并返回被移除的元素作为 Option。因此,在这个例子中,由于数组的类型是 Vec,所以 pop 方法返回的类型是 Option。外层的 Option 表示从数组中获取到的值,内层的 Option 表示数组中原本存储的 Option 类型的值。
    不过这一点在代码注释里也已经交代过了。
    1. #[derive(Debug)]
    2. enum Message {
    3.     // TODO: define the different variants used below
    4.     Quit, // 没有任何关联数据
    5.     Echo(String),// String字符串
    6.     Move{ x: i32, y: i32 }, // 匿名结构体
    7.     ChangeColor(i32, i32, i32), //3个i32
    8. }
    9. impl Message {
    10.     fn call(&self) {
    11.         println!("{:?}", self);
    12.     }
    13. }
    14. fn main() {
    15.     let messages = [
    16.         Message::Move { x: 10, y: 30 },
    17.         Message::Echo(String::from("hello world")),
    18.         Message::ChangeColor(200, 255, 255),
    19.         Message::Quit,
    20.     ];
    21.     for message in &messages {
    22.         message.call();
    23.     }
    24. }
    复制代码
  • options3:match语句的所有权问题,编译器报错说最后一行的y的value已经被moved了,很明显是match使用后,离开作用域y就失效了。解决是在  match&y 加上&即可。当然也可以 let y = match y{...}
    1. enum Message {
    2.     // TODO: implement the message variant types based on their usage below
    3.     Quit, // 没有任何关联数据
    4.     Echo(String),// String字符串
    5.     Move(Point), // 结构体
    6.     ChangeColor((u8, u8, u8)), //3个u8
    7. }
    8. struct Point {
    9.     x: u8,
    10.     y: u8,
    11. }
    12. struct State {
    13.     color: (u8, u8, u8),
    14.     position: Point,
    15.     quit: bool,
    16.     message: String
    17. }
    18. impl State {
    19.     fn change_color(&mut self, color: (u8, u8, u8)) {
    20.         self.color = color;
    21.     }
    22.     fn quit(&mut self) {
    23.         self.quit = true;
    24.     }
    25.     fn echo(&mut self, s: String) { self.message = s }
    26.     fn move_position(&mut self, p: Point) {
    27.         self.position = p;
    28.     }
    29.     fn process(&mut self, message: Message) {
    30.         // TODO: create a match expression to process the different message
    31.         // variants
    32.         // Remember: When passing a tuple as a function argument, you'll need
    33.         // extra parentheses: fn function((t, u, p, l, e))
    34.         match message {
    35.             Message::Quit => {self.quit = true;},
    36.             Message::Echo(args) => {self.echo(args);},
    37.             Message::Move(args) => {self.move_position(args);},
    38.             Message::ChangeColor(args) => {self.change_color(args);},
    39.         }
    40.     }
    41. }
    42. #[cfg(test)]
    43. mod tests {
    44.     use super::*;
    45.     #[test]
    46.     fn test_match_message_call() {
    47.         let mut state = State {
    48.             quit: false,
    49.             position: Point { x: 0, y: 0 },
    50.             color: (0, 0, 0),
    51.             message: "hello world".to_string(),
    52.         };
    53.         state.process(Message::ChangeColor((255, 0, 255)));
    54.         state.process(Message::Echo(String::from("hello world")));
    55.         state.process(Message::Move(Point { x: 10, y: 15 }));
    56.         state.process(Message::Quit);
    57.         assert_eq!(state.color, (255, 0, 255));
    58.         assert_eq!(state.position.x, 10);
    59.         assert_eq!(state.position.y, 15);
    60.         assert_eq!(state.quit, true);
    61.         assert_eq!(state.message, "hello world");
    62.     }
    63. }
    复制代码
16. error_handlings

错误处理,圣经相关内容,只不过题目更倾向于Result而不是panic

  • errors1:考察错误处理强相关的数据结构enum Result 在函数返回值中的应用。
    1. fn main() {
    2.     let answer = current_favorite_color();
    3.     println!("My current favorite color is {}", answer);
    4. }
    5. fn current_favorite_color() -> String {
    6.     String::from("blue")
    7. }
    复制代码
  • errors2: 介绍了 Result 和 ?运算符的使用。
    摘录:其实 ? 就是一个宏,它的作用跟上面的 match 几乎一模一样:
    1. fn main() {
    2.     let word = String::from("green"); // Try not changing this line :)
    3.     if is_a_color_word(&word) {
    4.         println!("That is a color word I know!");
    5.     } else {
    6.         println!("That is not a color word I know.");
    7.     }
    8. }
    9. fn is_a_color_word(attempt: &str) -> bool {
    10.     attempt == "green" || attempt == "blue" || attempt == "red"
    11. }
    复制代码
    如果结果是 Ok(T),则把 T 赋值给 f,如果结果是 Err(E),则返回该错误,所以 ? 特别适合用来传播错误。
    这里我还尝试了一下expect/unwrap/?/的区别,前两者是,如果到了Err的范畴,就直接panic推出了,前两者的区别是expect可以在报错中显示一些信息。而问号则是如果到了Err则返回一个Err,如果Ok则取出Ok其中的内容。所以这里只能用?
    1. fn trim_me(input: &str) -> String {
    2.     // TODO: Remove whitespace from both ends of a string!
    3.     String::from(input.trim())
    4. }
    5. fn compose_me(input: &str) -> String {
    6.     // TODO: Add " world!" to the string! There's multiple ways to do this!
    7.     input.to_string()+" world!"
    8. }
    9. fn replace_me(input: &str) -> String {
    10.     // TODO: Replace "cars" in the string with "balloons"!
    11.     input.to_string().replace("cars", "balloons")
    12. }
    复制代码
  • errors3:跟errors2很像,但是报错了,百思不得其解,但是看到编译器的报错信息就大致明白了,因为main函数默认没有返回值,或者说返回值类型是(),在这种函数中不能使用?,故而有两种解决方案:

    • 给main函数增加返回值类型,fn main() -> Result
    • 不使用?,用unwrap代替:letcost=total_cost(pretend_user_input).unwrap();

  • errors4:考察在一个具体程序如何用Result枚举类型来处理一些不合业务逻辑的问题。
    1. fn string_slice(arg: &str) {
    2.     println!("{}", arg);
    3. }
    4. fn string(arg: String) {
    5.     println!("{}", arg);
    6. }
    7. fn main() {
    8.     string_slice("blue");
    9.     string("red".to_string());
    10.     string(String::from("hi"));
    11.     string("rust is fun!".to_owned());
    12.     string("nice weather".into());
    13.     string(format!("Interpolation {}", "Station"));
    14.     string_slice(&String::from("abc")[0..1]);
    15.     string_slice("  hello there ".trim());
    16.     string("Happy Monday!".to_string().replace("Mon", "Tues"));
    17.     string("mY sHiFt KeY iS sTiCkY".to_lowercase());
    18. }
    复制代码
  • errors5:给出了一个比我上面errors4更优雅的解决方案,这道题的问题跟errors3类似,即main函数的返回值。因为 ? 要求 Result 形式的返回值,而 main 函数的返回是 (),因此无法满足,那是不是就无解了呢?实际上 Rust 还支持另外一种形式的 main 函数:
    1.     pub fn make_sausage() {
    2.         get_secret_recipe();
    3.         println!("sausage!");
    4.     }
    复制代码
  • errors6:这部分已经不在前面说的基础部分内容里了,而在圣经后面的进阶部分自定义错误类型。首先介绍了组合器,这个概念对我来说比较新,filter我不是很理解,重点我是来看map()和map_err()的。map用于修改Some() 或者 Ok() 中的值,对应map_err修改Err中的值。
    按照我的理解,对于一些错误系统的error.rs会给出自动的报错类型,如果我们不想让他直接pannic崩掉,而是做出一些合适的错误处理,就需要捕获这些报错类型并且处理掉,在这一行:
    1. mod delicious_snacks {
    2.     // TODO: Fix these use statements
    3.     pub use self::fruits::PEAR as fruit;
    4.     pub use self::veggies::CUCUMBER as veggie;
    5.     mod fruits {
    6.         pub const PEAR: &'static str = "Pear";
    7.         pub const APPLE: &'static str = "Apple";
    8.     }
    9.     mod veggies {
    10.         pub const CUCUMBER: &'static str = "Cucumber";
    11.         pub const CARROT: &'static str = "Carrot";
    12.     }
    13. }
    14. fn main() {
    15.     println!(
    16.         "favorite snacks: {} and {}",
    17.         delicious_snacks::fruit,
    18.         delicious_snacks::veggie
    19.     );
    20. }
    复制代码
    原本是parse().unwrap(),这将会导致如果s不能解析为整数i64,就会直接panic掉,而使用map_err对Err()进行一些修改,捕获原本的ParseInt类型错误,而将它转化成ParsePosNonzeroError:arseInt,这就符合下面的测试逻辑了。
    1. use std::num::ParseIntError;// This is a custom error type that we will be using in `parse_pos_nonzero()`.#[derive(PartialEq, Debug)]enum ParsePosNonzeroError {    Creation(CreationError),    ParseInt(ParseIntError),}impl ParsePosNonzeroError {    fn from_creation(err: CreationError) -> ParsePosNonzeroError {        ParsePosNonzeroError::Creation(err)    }    // TODO: add another error conversion function here.    fn from_parseint(err: ParseIntError) -> ParsePosNonzeroError {        ParsePosNonzeroError::ParseInt(err)    }}fn parse_pos_nonzero(s: &str) -> Result {    // TODO: change this to return an appropriate error instead of panicking    // when `parse()` returns an error.    mod delicious_snacks {
    2.     // TODO: Fix these use statements
    3.     pub use self::fruits::PEAR as fruit;
    4.     pub use self::veggies::CUCUMBER as veggie;
    5.     mod fruits {
    6.         pub const PEAR: &'static str = "Pear";
    7.         pub const APPLE: &'static str = "Apple";
    8.     }
    9.     mod veggies {
    10.         pub const CUCUMBER: &'static str = "Cucumber";
    11.         pub const CARROT: &'static str = "Carrot";
    12.     }
    13. }
    14. fn main() {
    15.     println!(
    16.         "favorite snacks: {} and {}",
    17.         delicious_snacks::fruit,
    18.         delicious_snacks::veggie
    19.     );
    20. }    PositiveNonzeroInteger::new(x).map_err(ParsePosNonzeroError::from_creation)}// Don't change anything below this line.#[derive(PartialEq, Debug)]struct PositiveNonzeroInteger(u64);#[derive(PartialEq, Debug)]enum CreationError {    Negative,    Zero,}impl PositiveNonzeroInteger {    fn new(value: i64) -> Result {        match value {            x if x < 0 => Err(CreationError::Negative),            x if x == 0 => Err(CreationError::Zero),            x => Ok(PositiveNonzeroInteger(x as u64)),        }    }}#[cfg(test)]mod test {    use super::*;    #[test]    fn test_parse_error() {        // We can't construct a ParseIntError, so we have to pattern match.        assert!(matches!(            parse_pos_nonzero("not a number"),            Err(ParsePosNonzeroError::ParseInt(_))        ));    }    #[test]    fn test_negative() {        assert_eq!(            parse_pos_nonzero("-555"),            Err(ParsePosNonzeroError::Creation(CreationError::Negative))        );    }    #[test]    fn test_zero() {        assert_eq!(            parse_pos_nonzero("0"),            Err(ParsePosNonzeroError::Creation(CreationError::Zero))        );    }    #[test]    fn test_positive() {        let x = PositiveNonzeroInteger::new(42);        assert!(x.is_ok());        assert_eq!(parse_pos_nonzero("42"), Ok(x.unwrap()));    }}
    复制代码
再解释一下 mod delicious_snacks {
    // TODO: Fix these use statements
    pub use self::fruits:EAR as fruit;
    pub use self::veggies::CUCUMBER as veggie;

    mod fruits {
        pub const PEAR: &'static str = "ear";
        pub const APPLE: &'static str = "Apple";
    }

    mod veggies {
        pub const CUCUMBER: &'static str = "Cucumber";
        pub const CARROT: &'static str = "Carrot";
    }
}

fn main() {
    println!(
        "favorite snacks: {} and {}",
        delicious_snacks::fruit,
        delicious_snacks::veggie
    );
} 为什么是这么个顺序,虽然它表达的意思我们大致能够理解。
这行代码的意思是,如果 s.parse() 解析成功,则将解析后的整数值赋值给 x;如果解析失败,? 操作符会立即返回并将 ParseIntError 转换为 ParsePosNonzeroError:arseInt 错误,并将其作为 parse_pos_nonzero 函数的返回结果。
在 Rust 中,? 操作符用于简化错误处理的过程。它只能在返回 Result 或 Option 的函数中使用。当使用 ? 操作符时,编译器会自动为你处理错误的传播。
具体到代码中,s.parse() 返回的是一个 Result,该结果表示解析字符串为整数的过程。如果解析成功,返回 Ok 包含解析后的整数值;如果解析失败,则返回 Err 包含一个 ParseIntError 错误。
17. generics

大名鼎鼎的泛型,我在C++那边都还没有好好学习,就过来rust这边再看一遍了。泛型对编程语言是极其重要的,它意味着可以用同一功能的函数处理不同类型的数据。

  • generics1: vector的建立罢了,为什么提vector啊,因为vector不会默认创建哪个数据类型,必须指定一个类型来建立,这是一个泛型的雏形吧。
    1. fn fruit_basket() -> HashMap<String, u32> {
    2.     let mut basket = HashMap::new();// TODO: declare your hash map here.
    3.     // Two bananas are already given for you :)
    4.     basket.insert(String::from("banana"), 2);
    5.     // TODO: Put more fruits in your basket here.
    6.     basket.insert(String::from("apple"),1);
    7.     basket.insert(String::from("orange"),3);
    8.     basket
    9. }
    复制代码
  • generics2:考察结构体及其方法泛型的使用。注意这里impl后也要加T,这样 Rust 就知道 Point 的尖括号中的类型是泛型而不是具体类型。这里的 Point 不再是泛型声明,而是一个完整的结构体类型,因为我们定义的结构体就是 Point 而不再是 Point。
    1.     for fruit in fruit_kinds {
    2.         // TODO: Insert new fruits if they are not already present in the
    3.         // basket. Note that you are not allowed to put any type of fruit that's
    4.         // already present!
    5.         // 查询Yellow对应的值,若不存在则插入新值
    6.         basket.entry(fruit).or_insert(1);
    7.     }
    复制代码
    因为已经这么说了,所以是存在impl块中直接定义具体类型结构体的对应方法的。
    1.     for fruit in fruit_kinds {
    2.         // TODO: Put new fruits if not already present. Note that you
    3.         // are not allowed to put any type of fruit that's already
    4.         // present!
    5.         if !basket.contains_key(&fruit) {
    6.             basket.insert(fruit, 1);
    7.         }
    8.     }
    复制代码
    解答如下:
    1. use std::collections::HashMap;
    2. // A structure to store the goal details of a team.
    3. struct Team {
    4.     goals_scored: u8,
    5.     goals_conceded: u8,
    6. }
    7. fn build_scores_table(results: String) -> HashMap<String, Team> {
    8.     // The name of the team is the key and its associated struct is the value.
    9.     let mut scores: HashMap<String, Team> = HashMap::new();
    10.     // 从用逗号和换行符组成的 字符串 构造 HashMap
    11.     // for循环是每次处理一行,也就是处理换行符
    12.     for r in results.lines() {
    13.         // split处理逗号
    14.         let v: Vec<&str> = r.split(',').collect();
    15.         let team_1_name = v[0].to_string();
    16.         let team_1_score: u8 = v[2].parse().unwrap();
    17.         let team_2_name = v[1].to_string();
    18.         let team_2_score: u8 = v[3].parse().unwrap();
    19.         // TODO: Populate the scores table with details extracted from the
    20.         // current line. Keep in mind that goals scored by team_1
    21.         // will be the number of goals conceded from team_2, and similarly
    22.         // goals scored by team_2 will be the number of goals conceded by
    23.         // team_1.
    24.         // 通过上述情况就可以知道是,现在要补充的是,将两个队伍及其比分情况放到HahsMap中
    25.         // 而这个小操作在hashmaps2的更新考查中已经用过了 basket.entry(fruit).or_insert(1);
    26.          // Update the team 1 score
    27.         let team_1 = scores.entry(team_1_name).or_insert(
    28.             Team {
    29.                 goals_scored: 0,
    30.                 goals_conceded: 0,
    31.             }
    32.         );
    33.         team_1.goals_scored += team_1_score;
    34.         team_1.goals_conceded += team_2_score;
    35.         // Update the team 2 score
    36.         let team_2 = scores.entry(team_2_name.clone()).or_insert(Team {
    37.             goals_scored: 0,
    38.             goals_conceded: 0,
    39.         });
    40.         team_2.goals_scored += team_2_score;
    41.         team_2.goals_conceded += team_1_score;
    42.     }
    43.     scores
    44. }
    45. #[cfg(test)]
    46. mod tests {
    47.     use super::*;
    48.     fn get_results() -> String {
    49.         let results = "".to_string()
    50.             + "England,France,4,2\n"
    51.             + "France,Italy,3,1\n"
    52.             + "Poland,Spain,2,0\n"
    53.             + "Germany,England,2,1\n";
    54.         results
    55.     }
    56.     #[test]
    57.     fn build_scores() {
    58.         let scores = build_scores_table(get_results());
    59.         let mut keys: Vec<&String> = scores.keys().collect();
    60.         keys.sort();
    61.         assert_eq!(
    62.             keys,
    63.             vec!["England", "France", "Germany", "Italy", "Poland", "Spain"]
    64.         );
    65.     }
    66.     #[test]
    67.     fn validate_team_score_1() {
    68.         let scores = build_scores_table(get_results());
    69.         let team = scores.get("England").unwrap();
    70.         assert_eq!(team.goals_scored, 5);
    71.         assert_eq!(team.goals_conceded, 4);
    72.     }
    73.     #[test]
    74.     fn validate_team_score_2() {
    75.         let scores = build_scores_table(get_results());
    76.         let team = scores.get("Spain").unwrap();
    77.         assert_eq!(team.goals_scored, 0);
    78.         assert_eq!(team.goals_conceded, 2);
    79.     }
    80. }
    复制代码
剩下的东西就是const泛型和泛型的性能,本来不打算看了,但是看到const泛型适用于内存较小,所以我继续看了一下。"在泛型参数之前,Rust 完全不适合复杂矩阵的运算,自从有了 const 泛型,一切即将改变。"
18. traits

特征Trait--圣经相关,特征跟接口的作用类似。跟大二学的java的抽象接口很像,以及C++的抽象类,就是特征trait中只定义共同行为,但是不描述具体行为的实现,具体行为的实现交给符合该特征的具体类型中来实现。
特征看起来好难。
题目虽然做完了,但是对特征的高级一点的用法还是不太理解它的逻辑。


  • traits1:考察impl trait_name for struct的简单语法。这里注意self是引用类型,不要把self当string。
    1. pub enum Command {
    2.     Uppercase,
    3.     Trim,
    4.     Append(usize),
    5. }
    6. mod my_module {
    7.     use super::Command;
    8.     // TODO: Complete the function signature!
    9.     pub fn transformer(input: Vec<(String, Command)>) -> Vec<String> {
    10.         // TODO: Complete the output declaration!
    11.         let mut output: Vec<String> = vec![];
    12.         // 就是将 string及其指令 转化的过程
    13.         for (string, command) in input.iter() {
    14.             // TODO: Complete the function body. You can do it!
    15.             let member = match command{
    16.                 Command::Uppercase => string.to_uppercase(),
    17.                 Command::Trim => string.trim().to_string(),//into()也可以,to_owned()也可以
    18.                 Command::Append(nums) => string.to_owned()+&"bar".repeat(*nums),//想寻找一个简单的写法,repeat就满足
    19.             };
    20.             output.push(member);
    21.         }
    22.         output
    23.     }
    24. }
    25. #[cfg(test)]
    26. mod tests {
    27.     // TODO: What do we need to import to have `transformer` in scope?
    28.     use super::my_module::transformer;
    29.     use super::Command;
    30.     #[test]
    31.     fn it_works() {
    32.         let output = transformer(vec![
    33.             ("hello".into(), Command::Uppercase),
    34.             (" all roads lead to rome! ".into(), Command::Trim),
    35.             ("foo".into(), Command::Append(1)),
    36.             ("bar".into(), Command::Append(5)),
    37.         ]);
    38.         assert_eq!(output[0], "HELLO");
    39.         assert_eq!(output[1], "all roads lead to rome!");
    40.         assert_eq!(output[2], "foobar");
    41.         assert_eq!(output[3], "barbarbarbarbarbar");
    42.     }
    43. }
    复制代码
  • traits2:还是很简单,并且跟traits1很像,但是这里突然让我想起来考虑一下vector的push方法的返回值类型和所有权的相关情况,见后面的知识补充。
    1. fn maybe_icecream(time_of_day: u16) -> Option<u16> {
    2.     // We use the 24-hour system here, so 10PM is a value of 22 and 12AM is a
    3.     // value of 0 The Option output should gracefully handle cases where
    4.     // time_of_day > 23.
    5.     // TODO: Complete the function body - remember to return an Option!
    6.     if time_of_day > 23 {
    7.         Option::None
    8.     } else {
    9.         if time_of_day < 22 {
    10.             Option::Some(5)
    11.         } else {
    12.             Option::Some(0)
    13.         }
    14.     }
    15. }
    16. #[cfg(test)]
    17. mod tests {
    18.     use super::*;
    19.     #[test]
    20.     fn check_icecream() {
    21.         assert_eq!(maybe_icecream(9), Some(5));
    22.         assert_eq!(maybe_icecream(10), Some(5));
    23.         assert_eq!(maybe_icecream(23), Some(0));
    24.         assert_eq!(maybe_icecream(22), Some(0));
    25.         assert_eq!(maybe_icecream(25), None);
    26.     }
    27.     #[test]
    28.     fn raw_value() {
    29.         // TODO: Fix this test. How do you get at the value contained in the
    30.         // Option?
    31.         let icecreams = maybe_icecream(12);
    32.         assert_eq!(icecreams, Some(5));
    33.     }
    34. }
    复制代码
  • traits3:考察的是特征中可以实现一个默认方法,这样实现这个特征的类型,既可以重载这个方法自己写,也可以直接使用这个默认方法。
    1. #[cfg(test)]
    2. mod tests {
    3.     #[test]
    4.     fn simple_option() {
    5.         let target = "rustlings";
    6.         let optional_target = Some(target);
    7.         // TODO: Make this an if let statement whose value is "Some" type
    8.         if let Some(word) = optional_target {
    9.             assert_eq!(word, target);
    10.         }
    11.     }
    12.     #[test]
    13.     fn layered_option() {
    14.         let range = 10;
    15.         let mut optional_integers: Vec<Option<i8>> = vec![None];
    16.         for i in 1..(range + 1) {
    17.             optional_integers.push(Some(i));
    18.         }
    19.         let mut cursor = range;
    20.         // TODO: make this a while let statement - remember that vector.pop also
    21.         // adds another layer of Option<T>. You can stack `Option<T>`s into
    22.         // while let and if let.
    23.         while let Some(Some(integer)) = optional_integers.pop() {
    24.             assert_eq!(integer, cursor);
    25.             cursor -= 1;
    26.         }
    27.         assert_eq!(cursor, 0);
    28.     }
    29. }
    复制代码
  • traits4:一个很惊艳的rust语言特性,使用特征作为函数参数,这里圣经用的是引用类型,而这里要灵活变通,因为调用实参限制是传入本身所有权而不是引用。
    1. enum MyEnum {
    2.     SomeValue(String),
    3.     AnotherValue(u32),
    4. }
    5. fn main() {
    6.     let my_var = MyEnum::SomeValue(String::from("Hello, Rust"));
    7.     match my_var {
    8.         MyEnum::SomeValue(s) => {
    9.             println!("Got ownership of string: {}", s);
    10.             // 在此分支中,我们获取了字符串的所有权,可以自由地使用它
    11.         }
    12.         MyEnum::AnotherValue(n) => {
    13.             println!("Got ownership of u32: {}", n);
    14.             // 在此分支中,我们获取了 u32 的所有权,可以自由地使用它
    15.         }
    16.     }
    17.     // 注意,在 `match` 表达式之后,`my_var` 的所有权并未返回,因为在所有可能的分支中都已经被取走。
    18. }
    复制代码
  • traits5:特征作为函数参数只是一个语法糖,它真正的本质是特征约束 ,上面traits4的语法糖就是简单的一重约束,展开写为形如:
    1. pub fn generate_nametag_text(name: String) -> Result<String,String> {
    2.     if name.is_empty() {
    3.         // Empty names aren't allowed.
    4.         Err("`name` was empty; it must be nonempty.".into())
    5.     } else {
    6.         Ok(format!("Hi! My name is {}", name))
    7.     }
    8. }
    9. #[cfg(test)]
    10. mod tests {
    11.     use super::*;
    12.     #[test]
    13.     fn generates_nametag_text_for_a_nonempty_name() {
    14.         assert_eq!(
    15.             generate_nametag_text("Beyoncé".into()),
    16.             Ok("Hi! My name is Beyoncé".into())
    17.         );
    18.     }
    19.     #[test]
    20.     fn explains_why_generating_nametag_text_fails() {
    21.         assert_eq!(
    22.             generate_nametag_text("".into()),
    23.             // Don't change this line
    24.             Err("`name` was empty; it must be nonempty.".into())
    25.         );
    26.     }
    27. }
    复制代码
    而多重约束时,就是如下形式:
    1. let mut f = match f {
    2.     // 打开文件成功,将file句柄赋值给f
    3.     Ok(file) =>> file,
    4.     // 打开文件失败,将错误返回(向上传播)
    5.     Err(e) =>> return Err(e),
    6. };
    复制代码
    这道题也就可以据此解答为:可以同时调佣两个特征中的方法。
    1. use std::num::ParseIntError;
    2. pub fn total_cost(item_quantity: &str) -> Result<i32, ParseIntError> {
    3.     let processing_fee = 1;
    4.     let cost_per_item = 5;
    5.     let qty = item_quantity.parse::<i32>()?;
    6.     Ok(qty * cost_per_item + processing_fee)
    7. }
    8. #[cfg(test)]
    9. mod tests {
    10.     use super::*;
    11.     #[test]
    12.     fn item_quantity_is_a_valid_number() {
    13.         assert_eq!(total_cost("34"), Ok(171));
    14.     }
    15.     #[test]
    16.     fn item_quantity_is_an_invalid_number() {
    17.         assert_eq!(
    18.             total_cost("beep boop").unwrap_err().to_string(),
    19.             // unwrap_err()的意思是,将ok()或者Err()中的值取出来并报错。
    20.             "invalid digit found in string"
    21.         );
    22.     }
    23. }
    复制代码
知识补充:vector.push()返回值与所有权

在 Rust 中,Vec 的 push 方法用于将一个元素添加到向量(Vec)的末尾。让我们来详细讲解一下它的返回值类型和所有权。
push 方法的签名如下:
  1. // Hmm...? Why is this only returning an Ok value?
  2. if value > 0{
  3.    Ok(PositiveNonzeroInteger(value as u64))
  4. } else if value == 0 {
  5.       Err(CreationError::Zero)
  6.   } else {
  7.       Err(CreationError::Negative)
  8. }
复制代码
返回值类型是 (),也被称为“单元类型”或“空元组”。这意味着 push 方法不返回具体的值,仅用于修改 Vec 本身。
关于所有权,当调用 push 方法时,向量会获取传入元素(value)的所有权。也就是说,在调用 push 方法之后,传入的元素将成为向量的一部分,向量会负责管理它的生命周期和内存。这意味着传入的元素不再属于原来的所有者,而是属于向量。
此外,需要注意的是,为了能够修改向量,我们需要将 &mut self 作为方法的第一个参数。这表示我们需要拥有向量的可变引用,以便能够对其进行修改。
下面是一个使用 push 方法的示例:
  1. use std::error::Error;
  2. use std::fs::File;
  3. fn main() -> Result<(), Box<dyn Error>> {
  4.     let f = File::open("hello.txt")?;
  5.     Ok(())
  6. }
复制代码
在上述代码中,我们创建了一个空的 Vec,然后调用 push 方法将整数 10 添加到向量的末尾。在调用 push 方法之后,vec 将会拥有整数 10 的所有权。
19. quiz3

这里是多重特征的一个实现实例。注意这里由于impl块中使用了format!这种宏,所以还要声明使用了Display特征。通过在 impl 声明中指定 T: std::fmt:isplay,表明泛型类型 T 必须实现 std::fmt:isplay trait。
  1. let x: i64 = s.parse().map_err(ParsePosNonzeroError::from_parseint)?;
复制代码
20. lifetimes

生命周期!用rust写编译器的时候最烦这方面的问题,这次来好好看看。"生命周期很可能是 Rust 中最难的部分."圣经书上相关部分
简单来说,程序员如果对生命周期判断不好,就会引发程序的隐藏问题,并且很难被发现。而rust在编译器层次实现了生命周期的检查。与之适配的,为了通过生命周期检查,写rust的时候有时候需要手动标注生命周期(其他语言和此前的rust都是编译器自动推导生命周期)。
生命周期主要是解决悬垂引用问题。可以对生命周期进行下总结:生命周期语法用来将函数的多个引用参数和返回值的作用域关联到一起,一旦关联到一起后,Rust 就拥有充分的信息来确保我们的操作是内存安全的。

  • lifetimes1:就是圣经书上这部分的原例。
  • lifetimes2:还是书上原例,原因是result的生命周期跟函数longest返回的最短生命周期一样长,而longest返回的最短声明周期是string2,string2由于在大括号内定义,到大括号结束就死了,所以result在大括号外也无法使用。修改就是只需要将string2的定义/绑定放到大括号外。
  • lifetimes3:很简单,就是结构体的生命周期如何声明,没有涉及逻辑问题。
剩下的内容就是rust编译器更贴心一点的生命周期消除规则、方法中的生命周期(主要在说self的第三规则和生命周期约束)、静态生命周期,差不多看明白了,感觉比特征更好理解一些,特征还要回去看看。
到这里我不太熟悉的是 modules 和 generics 这两部分,其他的感觉还好。生命周期仔细看完圣经觉得还可以接受。
21. tests


  • tests1:介绍一下test模块是长什么样的。assert!(true); 让它无事发生即可。
  • tests2:assert_eq!(1,1); 这个断言就是检查是否相等,前面rustlings很多题都出现过了。
  • tests3:刚开始默认是assert_eq!,然后发现是assert!.
    1. use std::num::ParseIntError;
    2. // This is a custom error type that we will be using in `parse_pos_nonzero()`.
    3. #[derive(PartialEq, Debug)]
    4. enum ParsePosNonzeroError {
    5.     Creation(CreationError),
    6.     ParseInt(ParseIntError),
    7. }
    8. impl ParsePosNonzeroError {
    9.     fn from_creation(err: CreationError) -> ParsePosNonzeroError {
    10.         ParsePosNonzeroError::Creation(err)
    11.     }
    12.     // TODO: add another error conversion function here.
    13.     fn from_parseint(err: ParseIntError) -> ParsePosNonzeroError {
    14.         ParsePosNonzeroError::ParseInt(err)
    15.     }
    16. }
    17. fn parse_pos_nonzero(s: &str) -> Result<PositiveNonzeroInteger, ParsePosNonzeroError> {
    18.     // TODO: change this to return an appropriate error instead of panicking
    19.     // when `parse()` returns an error.
    20.     let x: i64 = s.parse().map_err(ParsePosNonzeroError::from_parseint)?;
    21.     PositiveNonzeroInteger::new(x).map_err(ParsePosNonzeroError::from_creation)
    22. }
    23. // Don't change anything below this line.
    24. #[derive(PartialEq, Debug)]
    25. struct PositiveNonzeroInteger(u64);
    26. #[derive(PartialEq, Debug)]
    27. enum CreationError {
    28.     Negative,
    29.     Zero,
    30. }
    31. impl PositiveNonzeroInteger {
    32.     fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> {
    33.         match value {
    34.             x if x < 0 => Err(CreationError::Negative),
    35.             x if x == 0 => Err(CreationError::Zero),
    36.             x => Ok(PositiveNonzeroInteger(x as u64)),
    37.         }
    38.     }
    39. }
    40. #[cfg(test)]
    41. mod test {
    42.     use super::*;
    43.     #[test]
    44.     fn test_parse_error() {
    45.         // We can't construct a ParseIntError, so we have to pattern match.
    46.         assert!(matches!(
    47.             parse_pos_nonzero("not a number"),
    48.             Err(ParsePosNonzeroError::ParseInt(_))
    49.         ));
    50.     }
    51.     #[test]
    52.     fn test_negative() {
    53.         assert_eq!(
    54.             parse_pos_nonzero("-555"),
    55.             Err(ParsePosNonzeroError::Creation(CreationError::Negative))
    56.         );
    57.     }
    58.     #[test]
    59.     fn test_zero() {
    60.         assert_eq!(
    61.             parse_pos_nonzero("0"),
    62.             Err(ParsePosNonzeroError::Creation(CreationError::Zero))
    63.         );
    64.     }
    65.     #[test]
    66.     fn test_positive() {
    67.         let x = PositiveNonzeroInteger::new(42);
    68.         assert!(x.is_ok());
    69.         assert_eq!(parse_pos_nonzero("42"), Ok(x.unwrap()));
    70.     }
    71. }
    复制代码
  • tests4: #[should_panic]特性。当被测试代码panic时来进行处理。
22. Iterators

迭代器

  • iterators1: 迭代器的惰性初始化.iter()和next() 访问方法。
    1. fn main() {
    2.     let mut shopping_list: Vec<&str> = Vec::new();
    3.     shopping_list.push("milk");
    4. }
    复制代码
  • iterators2: 这道题没有很好写,因为发现自己的字符串那一节也没有好好看完,很多方法都还不会。
    1. struct Point<T> {
    2.     x: T,
    3.     y: T,
    4. }
    5. impl<T> Point<T> {
    6.     fn x(&self) -> &T {
    7.         &self.x
    8.     }
    9. }
    复制代码
  • iterators3: test比较多,考察的也比较多,首先困扰了我一下的是Err的return问题,为什么必须要return才行,为什么不能通过rust不加分号的语法糖直接返回,后面觉得Err()是一个表达式。接着就是重点的一个Result数组和数组Result,这个例子就体现了collect的功能强大,可以自动收集为目标类型。
    1. impl Point `<f32>` {
    2. fn distance_from_origin(&self) -> f32 {
    3. (self.x.powi(2) + self.y.powi(2)).sqrt()
    4. }
    5. }
    复制代码
  • iterators4: 领略一下迭代器的精彩之处。(圣经没有介绍)
    1. struct Wrapper<T> {
    2.     value: T,
    3. }
    4. impl<T> Wrapper<T> {
    5.     pub fn new(value: T) -> Self {
    6.         Wrapper { value }
    7.     }
    8. }
    复制代码
  • iterators5:
    1. impl AppendBar for String {
    2.     // TODO: Implement `AppendBar` for type `String`.
    3.     fn append_bar(self) -> Self {
    4.         self+"Bar"
    5.     }
    6. }
    复制代码
23. smart_pointers

智能指针

  • Box,可以将值分配到堆上
  • Rc,引用计数类型,允许多所有权存在
  • Ref 和 RefMut,允许将借用规则检查从编译期移动到运行期进行


  • box1: 我的用rust写的C子集编译器项目,就大量使用了Box类型智能指针。而这个题目是圣经中给出的例子。
    1. // TODO: Implement trait `AppendBar` for a vector of strings.
    2. impl AppendBar for Vec<String> {
    3.     fn append_bar(mut self) -> Vec<String> {
    4.         self.push("Bar".to_owned());
    5.         self
    6.     }
    7. }
    复制代码
  • cow1: 这道题并不困难,但是要理解逻辑不容易,首先这个cows是什么意思呢?翻了翻,这个圣经中以及其他基本有名的rust书中没有介绍这个概念,下面我是在rust官方文档-std::borrow::cow里学习的。cow大致是在解决引用类型数据的所有权与可修改有时会发生矛盾的问题(处理借用和所有权转移),cow写时复制,当不修改内容时,就返回不可变引用;修改内容时,复制一份产生可变引用。具体来说:

    • to_mut()方法的作用是获取数据的可变引用,如果数据当前处于Owned状态,则直接返回对该数据的可变引用;否则,通过克隆数据来获取可变引用,并将数据的所有权转移为Owned状态。
    • 具体来说,如果Cow当前处于Borrowed状态,则说明Cow的底层数据目前并不拥有所有权。此时调用to_mut()方法将克隆数据并返回对其的可变引用,从而获取了底层数据的所有权。此后,对返回的可变引用进行的修改将直接反映在底层数据上。

  • rc1: Reference counting 引用计数智能指针。题目比较简单。只需要继续clone,下面drop即可。
  • arc1:这个我也是在官方文档里看的。跟rc很相似,只是额外实现了atomic
    1. pub trait Licensed {
    2.     fn licensing_info(&self) -> String{
    3.         String::from("Some information")
    4.     }
    5. }
    复制代码
这部分说实话,真的看的云里雾里,只是把题目做了,记住了一些语法和函数,机制还没有仔细理解。
24. threads

多线程 in rust圣经,说起来,C++的多线程只在os理论课结课的一个大作业中使用过,当时实现的是大文件数据的多线程归并排序,但是掌握的并不深刻,rust看到这,可以说在我技能树里已经超过C++了。

  • threads1:

    • 我们定义一个可变的 handles向量来存储所有的线程句柄。接下来,使用一个循环来创建10个线程,它们执行相同的操作,即将当前时间戳存储在 start中,然后使线程进入休眠状态250毫秒,并打印信息以表明线程已完成。
    • 最后,线程返回自它启动后经过的毫秒数,作为 u128类型的返回值。当线程启动时,它会获取当前时间戳并存储在 start变量中,当它完成时,它会再次获取当前时间戳,并计算时间差。然后,在vector中存储该时间差。
    • 接下来,我们使用一个循环来等待每个线程完成,然后从其返回值中提取时间差,并将其存储在vector中。如果任何线程不能成功地join,则程序将崩溃并显示错误消息。
      1. // YOU MAY ONLY CHANGE THE NEXT LINE
      2. fn compare_license_types(software: impl Licensed, software_two: impl Licensed) -> bool {
      3.     software.licensing_info() == software_two.licensing_info()
      4. }
      复制代码

  • threads2:

    • 在介绍互斥锁,锁的创建与使用。互斥锁的实现和智能指针密不可分。但我评价是太复杂了,我感觉虽然通过了编译,但是语义并不正确。
    • 主要是第二个for循环回收句柄handle的处理,join之后进行println!的话,有时候输出会很奇怪,如下图所示:

    • 从上面看来需要等第二个for循环将全部子线程join完之后,再在for外的主线程中输出Jobs completed 10是最正确的语法,所以说thread2的注释问句:每个子线程都要join吗是必须的,同时我们最后的jobs结果应该在主线程中输出。就像上图我的"==============="的下一行。

  • thread3: mpsc,多发送者单接受者,这道题是两个发送,一个接收。这里主要是一个所有权问题,这个跟rust圣经多发送者中使用的例子很像,就不赘述了。
    这里记录一下输出结果:这里是异步消息。还有同步消息来着。

25. macros

macro宏编程,我在rust-cc编译器的错误消息部分就是用宏来处理错误,可以说是rust高级进阶部分比较熟悉的一部分了。

  • macros1:宏的定义,结尾必须有叹号。my_macro!();
  • macros2:在调用之前,宏必须对调用者可见,将my_macro声明放在main之前即可。这样可以确保在 main 函数中调用宏时,宏定义已经被编译器看见并展开。可以推知rust编译器在处理这部分语法时是顺序进行不回头的。
  • macros3:与mod有一个小联动,现在结构如下所示:

    • macros3

      • mod my_macro
      • main

    所以mod中的宏对main不可见,需要导出。在mod前加#[macro_use]即可。
    在 Rust 中使用一个宏时,通常需要通过 use 语句将宏导入到当前作用域中。但是,对于某些宏,为了避免每次使用宏时都需要使用 use 语句进行导入,你可以通过在模块或 crate 中使用 #[macro_use] 属性来自动导入宏
    虽然这个语法快死了,但是我还是想说,如果mod和main不在一个文件里,可以形如下面处理:
    1. pub fn notify(item1: &impl Summary, item2: &impl Summary) {}
    2. //展开写为:
    3. pub fn notify<T: Summary>(item1: &T, item2: &T) {}
    复制代码
  • macros4: 修改宏本身了。但是这个问题是上一个分支里没有加分号。
    1. pub fn notify(item: &(impl Summary + Display)) {}
    2. // 展开
    3. pub fn notify<T: Summary + Display>(item: &T) {}
    复制代码
rustfmt::skip 是一种 #[attribute],用于告诉 Rustfmt 工具不要格式化注解所标记的代码。有时候,我们可能需要避免 Rustfmt 对某些代码进行格式化处理,例如对于已经手动格式化好的代码块或宏定义等。
26. clippy

clippy是一个rust代码分析工具,可以这么认为。可以检查代码中的潜在问题,如竞争条件、未使用的变量、不必要的类型强制转换等等。借助 Clippy 的 lint,您可以更轻松地发现一些常见的编码错误,并编写出更加优雅、可读性高的代码。可以通过运行 cargo clippy 命令来对当前项目进行代码分析。Clippy 将会输出所有侦测到的问题列表,可以根据这些提示优化代码。

  • clippy1:参考 https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant,当检查到近似于 std::f32::constsstd::f64::consts 的常量时就会报错。因为默认 #[deny(clippy::approx_constant)] 选项是开启的。我们可以用 f32::consts:I 替代
    1. // YOU MAY ONLY CHANGE THE NEXT LINE
    2. fn some_func(item: impl OtherTrait + SomeTrait) -> bool {
    3.     item.some_function() && item.other_function()
    4. }
    复制代码
  • clippy2:clippy 认为对 Option 结构 for 循环没有用 if let 的可读性好。参考 https://rust-lang.github.io/rust-clippy/master/index.html#for_loops_over_fallibles,clippy 会检查是否对 Option 或 Result 值进行了循环
    1. pub fn push(&mut self, value: T)
    复制代码
  • clippy3:做到这发现clippy大致是在解决rust中可能写的很蠢的代码。比如下面if的判断中unwrap用的很垃圾,然后arr的切片生成中-3后面没有逗号,然后resize方法使用错误等等
    1. let mut vec = Vec::new();
    2. vec.push(10);
    复制代码
27. conversions

类型转换
convert in 中文手册
From_str in 中文手册
我以为这部分是介绍两两类型如何互转,结果是一系列针对类型转换的处理,这种处理是基于Rust的Trait特性的。

  • using_as: Rust 中内置了一些基本类型之间的转换,使用 as 操作符来完成
  • from_into:

    • 首先检查输入字符串的长度,如果是0则返回默认的Person对象。这个步骤也可以直接使用 if s.is_empty()来代替。
    • 使用逗号分隔符将输入字符串分割成两个部分。这个步骤使用 s.split(',').collect()实现。
    • 提取分割后的第一个元素作为姓名(name)。
    • 如果姓名为空,则返回默认的Person对象。这个步骤可以直接使用 if name.is_empty()来判断。
    • 将分割后的第二部分作为字符串进行解析,得到年龄age,如果解析失败则返回默认的Person对象。这个步骤使用 parts[1].trim().parse:)来实现。trim()函数用于去除空格和换行符等。
    • 如果所有步骤都成功,则返回一个新的Person对象,其姓名是name,年龄是age。
    1. pub struct ReportCard<T> {
    2.     pub grade: T,
    3.     pub student_name: String,
    4.     pub student_age: u8,
    5. }
    6. impl<T: std::fmt::Display> ReportCard<T> {
    7.     pub fn print(&self) -> String {
    8.         format!("{} ({}) - achieved a grade of {}",
    9.             &self.student_name, &self.student_age, &self.grade)
    10.     }
    11. }
    12. #[cfg(test)]
    13. mod tests {
    14.     use super::*;
    15.     #[test]
    16.     fn generate_numeric_report_card() {
    17.         let report_card = ReportCard {
    18.             grade: 2.1,
    19.             student_name: "Tom Wriggle".to_string(),
    20.             student_age: 12,
    21.         };
    22.         assert_eq!(
    23.             report_card.print(),
    24.             "Tom Wriggle (12) - achieved a grade of 2.1"
    25.         );
    26.     }
    27.     #[test]
    28.     fn generate_alphabetic_report_card() {
    29.         // TODO: Make sure to change the grade here after you finish the exercise.
    30.         let report_card = ReportCard {
    31.             grade: "A+",
    32.             student_name: "Gary Plotter".to_string(),
    33.             student_age: 11,
    34.         };
    35.         assert_eq!(
    36.             report_card.print(),
    37.             "Gary Plotter (11) - achieved a grade of A+"
    38.         );
    39.     }
    40. }
    复制代码
  • from_str: 读完代码发现跟from_into的逻辑基本类似,只是将默认处理修改为了Err报错。由于测试test逻辑基本类似,所以基本不用大改,只是由于Err的类型,需要略微调整一下代码的顺序。

    • https://rustwiki.org/zh-CN/std/str/trait.FromStr.html
    • 这道题的原理基本是:在实现 FromStr 时,可以在 Strings 上使用 parse 方法以生成实现者类型的对象
      1. pub fn is_even(num: i32) -> bool {
      2.     num % 2 == 0
      3. }
      4. #[cfg(test)]
      5. mod tests {
      6.     use super::*;
      7.     #[test]
      8.     fn is_true_when_even() {
      9.         assert!(is_even(2));
      10.     }
      11.     #[test]
      12.     fn is_false_when_odd() {
      13.         assert!(!is_even(5));
      14.     }
      15. }
      复制代码

  • try_from_into:

    • https://rustwiki.org/zh-CN/std/convert/trait.TryFrom.html
    • TryFrom 是 Rust 标准库中的一个工具,它可以让我们从一个类型转换到另一个类型。但与 From 不同的是,TryFrom 会在转换失败时返回错误,而不是 panic。
    • 例如,假设我们有一个字符串表示年龄,我们想把它转换成一个 usize 类型。使用 FromStr trait 我们可以通过调用 parse() 函数轻松地实现这个转换,如果能成功转换,则返回解析后的值,否则返回一个 Err 对象。这种方式简单易懂,但是它违反了 Rust 的一些设计原则——避免隐藏错误。
    • 如果我们使用 FromStr 进行转换时没有考虑到错误情况,那么就会导致程序在转换失败时无法处理错误,最终产生不可预测的后果。
    • TryFrom 就是为了解决这个问题而诞生的。它强制我们在类型转换中考虑错误的情况,并且在转换失败时返回一个合适的错误类型,以便我们能够更好地处理错误。当我们使用 TryFrom 时,我们需要为目标类型实现 TryFrom trait,并使用 try_from() 函数进行转换。如果转换成功,则返回之前提及的 Ok 对象,否则返回一个 Err 对象,其中包含了转换失败的相关信息。
      1. fn main() {
      2.     let my_fav_fruits = vec!["banana", "custard apple", "avocado", "peach", "raspberry"];
      3.     let mut my_iterable_fav_fruits = my_fav_fruits.iter();   // TODO: Step 1
      4.     assert_eq!(my_iterable_fav_fruits.next(), Some(&"banana"));
      5.     assert_eq!(my_iterable_fav_fruits.next(), Some(&"custard apple"));     // TODO: Step 2
      6.     assert_eq!(my_iterable_fav_fruits.next(), Some(&"avocado"));
      7.     assert_eq!(my_iterable_fav_fruits.next(), Some(&"peach"));     // TODO: Step 3
      8.     assert_eq!(my_iterable_fav_fruits.next(), Some(&"raspberry"));
      9.     assert_eq!(my_iterable_fav_fruits.next(), None);     // TODO: Step 4
      10. }
      复制代码

  • as_ref_mut: https://rustwiki.org/zh-CN/std/convert/trait.AsRef.html

    • as_ref_mut 是一个方法,它允许我们将一个值转换为对同一值的可变引用。
    • 假设我们有一个包含整数的变量 x,它的类型是 i32。通常情况下,如果我们想修改 x 的值,我们需要拥有它的可变借用(mutable borrow)。但是有时候,我们可能已经拥有了一个不可变的引用,而我们又希望能够修改这个值。
    • 这时候就可以使用 as_ref_mut 方法来实现。
    • 具体来说,as_ref_mut 方法将一个值转换为对同一值的可变引用,但是只有在原始值(调用 as_ref_mut 方法的值)本身就是可变的时候才会返回可变引用。如果原始值是不可变的,那么 as_ref_mut 方法返回一个None。因此,我们可以通过检查 Option 类型的返回值来确定是否成功地获得了可变引用。
    • 举个例子,假设我们有一个字符串 s,我们希望将其转换为一个字节切片的可变引用。我们可以使用 as_mut 方法将字符串转换为 &mut [u8] 类型的可变引用。如果字符串是可变的,那么转换成功并返回一个 Some 值,其中包含可变引用;否则,返回 None。
    • 总之,as_ref_mut 方法是 Rust 提供的一种转换方法,它可以方便地将一个值转换为对同一值的可变引用,但只有在原始值本身是可变的情况下才会成功。这样可以帮助我们更好地控制可变性,并提供一种安全和灵活的方式来修改数据。
    • AsRef 是一个 trait(特质),用于将一个类型转换为另一种类型的引用。
    • AsRef 的主要作用是允许我们以统一的方式处理不同类型之间的转换。通过实现 AsRef trait,我们可以定义一个类型转换函数,该函数将一个类型转换为另一个类型的引用,而不是拥有新的所有权。
    • 具体来说,如果我们有一个类型 T,并且希望将其转换为类型 U 的引用,我们可以实现 AsRef <U> trait 来完成这个转换。在实现中,我们需要提供一个名为 as_ref 的方法,该方法返回类型 &U。这样,我们就可以使用 as_ref 方法来将 T 转换为 U 的引用。
    • 当我们在代码中需要将一个类型转换为另一个类型的引用时,可以使用 as_ref 方法来进行转换。这对于接受不同类型引用参数的函数或方法非常有用,因为它使得我们可以使用相同的代码来处理不同的类型。
    • AsRef trait 主要用于提供一种通用的引用转换机制,使得代码更加灵活和可重用。在标准库中,许多类型都实现了 AsRef trait,例如字符串、向量、切片等。这意味着我们可以使用 as_ref 方法来将这些类型转换为其他类型的引用,而不需要进行显式的类型转换。
    • 总之,AsRef 是 Rust 中的一个 trait,它提供了一种统一的方式来将一个类型转换为另一个类型的引用。通过实现 AsRef trait,我们可以定义自己的类型转换,并使用 as_ref 方法来执行转换操作。这为代码重用和类型灵活性提供了便利。
    • as_mut()和AsMut trait就也很好理解了。跟上面AsRef极其类似。至于为什么要使用as_mut,这是因为T类型因为不确定,所以编译器不清楚它的内部值是否可以直接修改,所以对泛型参数进行可变操作是被禁止的,所以我们用as_mut明确地告诉编译器该类型是可变的。
    • AsMut  是一个标准库中定义的 trait,它将类型 T 转换为一个指向其内部值的可变引用。当我们在泛型函数中使用 T: AsMut  这个 trait bound 时,我们告诉编译器要求 T 类型必须具备将其内部值作为 u32 类型的可变引用的能力。通过这个 trait bound,编译器就可以安全地假设我们可以使用 as_mut() 方法来获得一个指向 arg 内部值的可变引用。这样,我们就可以对该值进行修改,而不会破坏借用规则。
      1. // Step 1.
      2. // Complete the `capitalize_first` function.
      3. // "hello" -> "Hello"
      4. pub fn capitalize_first(input: &str) -> String {
      5.     let mut c = input.chars();
      6.     match c.next() {
      7.         None => String::new(), // 返回空: ""
      8.         Some(first) => first.to_uppercase().collect::<String>() + c.as_str(),
      9.     }
      10. }
      11. // Step 2.
      12. // Apply the `capitalize_first` function to a slice of string slices.
      13. // Return a vector of strings.
      14. // ["hello", "world"] -> ["Hello", "World"]
      15. pub fn capitalize_words_vector(words: &[&str]) -> Vec<String> {
      16.     // vec![]
      17.     words.iter().map(|&word| capitalize_first(word)).collect()
      18. }
      19. // Step 3.
      20. // Apply the `capitalize_first` function again to a slice of string slices.
      21. // Return a single string.
      22. // ["hello", " ", "world"] -> "Hello World"
      23. pub fn capitalize_words_string(words: &[&str]) -> String {
      24.     // String::new()
      25.     words.iter().map(|&word| capitalize_first(word)).collect()
      26. }
      27. #[cfg(test)]
      28. mod tests {
      29.     use super::*;
      30.     #[test]
      31.     fn test_success() {
      32.         assert_eq!(capitalize_first("hello"), "Hello");
      33.     }
      34.     #[test]
      35.     fn test_empty() {
      36.         assert_eq!(capitalize_first(""), "");
      37.     }
      38.     #[test]
      39.     fn test_iterate_string_vec() {
      40.         let words = vec!["hello", "world"];
      41.         assert_eq!(capitalize_words_vector(&words), ["Hello", "World"]);
      42.     }
      43.     #[test]
      44.     fn test_iterate_into_string() {
      45.         let words = vec!["hello", " ", "world"];
      46.         assert_eq!(capitalize_words_string(&words), "Hello World");
      47.     }
      48. }
      复制代码

X. 写在最后

2023年10月22日夜于图书馆七楼,趁着10月份旅游、回家修养后的旺盛精力,终于一鼓作气将rustlings的95道题完整梳理了一遍,把rust圣经更深入地看了一遍。
期间产生了很大的怀疑,因为rust也算博大高深,自己之前学习的效果在我现在看来并不明显,很多概念都还没有看明白,开始怀疑自己用rust写的c编译器很糟糕。
但是总算看完了,也是我梳理总结知识树完成的第一个小步。



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

前进之路

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表