# Rust By Practice Solution 這是我個人練習 Rust By Practice 的解答,題目與學習資料如下: > 配套學習資料:Rust Course | [Github](https://github.com/sunface/rust-course) | [線上閱讀](https://course.rs) > 本練習題庫:Rust By Practice | [Github](https://github.com/sunface/rust-by-practice) | [線上閱讀](https://practice-zh.course.rs/) # 3. Variable 变量绑定与解构 ### 绑定和可变性 1. 🌟 变量只有在初始化后才能被使用 ```rust= // 修复下面代码的错误并尽可能少的修改 fn main() { let x: i32 = 5; // 未初始化,但被使用 let _y: i32; // 未初始化,也未被使用 println!("x is equal to {}", x); } ``` 2. 🌟🌟 可以使用 `mut` 将变量标记为可变 ```rust= // 完形填空,让代码编译 fn main() { let mut x = 1; x += 2; println!("x = {}", x); } ``` ### 变量作用域 3. 🌟 作用域是一个变量在程序中能够保持合法的范围 ```rust= // 修复下面代码的错误并使用尽可能少的改变 fn main() { let x: i32 = 10; let y: i32 = 5; println!("x 的值是 {}, y 的值是 {}", x, y); println!("x 的值是 {}, y 的值是 {}", x, y); } ``` 4. 🌟🌟 ```rust= // 修复错误 fn main() { let x = define_x(); println!("{}, world", x); } fn define_x() -> String { String::from("hello") } ``` ### 变量遮蔽( Shadowing ) 5. 🌟🌟 若后面的变量声明的名称和之前的变量相同,则我们说:第一个变量被第二个同名变量遮蔽了( shadowing ) ```rust= // 只允许修改 `assert_eq!` 来让 `println!` 工作(在终端输出 `42`) fn main() { let x: i32 = 5; { let x = 12; assert_eq!(x, 12); } assert_eq!(x, 5); let x = 42; println!("{}", x); // 输出 "42". } ``` 6. 🌟🌟 修改一行代码以通过编译 ```rust= #![allow(unused)] fn main() { let mut x: i32 = 1; x = 7; // 遮蔽且再次绑定 let mut x = x; x += 3; let y = 4; // 遮蔽 let y = "I can also be bound to text!"; } ``` ### 未使用的变量 7. 使用以下方法来修复编译器输出的 warning : - 🌟 一种方法 - 🌟🌟 两种方法 :::warning 注意: 你可以使用两种方法解决,但是它们没有一种是移除 let x = 1 所在的代码行 ::: 方法一: ```rust= fn main() { let x = 1; println!("{}", x); } ``` 方法二: ```rust= fn main() { let _x = 1; } ``` ### 变量解构 8. 🌟🌟 我们可以将 `let` 跟一个模式一起使用来解构一个元组,最终将它解构为多个独立的变量 :::warning 提示: 可以使用变量遮蔽或可变性 ::: ```rust= // 修复下面代码的错误并尽可能少的修改 fn main() { let (mut x, y) = (1, 2); x += 2; assert_eq!(x, 3); assert_eq!(y, 2); } ``` ### 解构式赋值 该功能于 Rust 1.59 版本引入:你可以在赋值语句的左式中使用元组、切片或结构体进行匹配赋值。 9. 🌟🌟 :::warning Note: 解构式赋值只能在 Rust 1.59 或者更高版本中使用 ::: ```rust= fn main() { let (x, y); (x,..) = (3, 4); [.., y] = [1, 2]; // 填空,让代码工作 assert_eq!([x,y], [3, 2]); } ``` # 4. 基本类型 ## 4.1 数值类型 ### 整数 1. 🌟 :::warning Tips: 如果我们没有显式的给予变量一个类型,那编译器会自动帮我们推导一个类型 ::: ```rust= // 移除某个部分让代码工作 fn main() { let x: i32 = 5; let mut y = 5; y = x; let z = 10; // 这里 z 的类型是? Ans: i32 } ``` 2. 🌟 ```rust= // 填空 fn main() { let v: u16 = 38_u8 as u16; } ``` 3. 🌟🌟🌟 :::warning Tips: 如果我们没有显式的给予变量一个类型,那编译器会自动帮我们推导一个类型 ::: ```rust= // 修改 `assert_eq!` 让代码工作 fn main() { let x = 5; assert_eq!("i32".to_string(), type_of(&x)); } // 以下函数可以获取传入参数的类型,并返回类型的字符串形式,例如 "i8", "u8", "i32", "u32" fn type_of<T>(_: &T) -> String { format!("{}", std::any::type_name::<T>()) } ``` 4. 🌟🌟 ```rust= // 填空,让代码工作 fn main() { assert_eq!(i8::MAX, 127); // -128..=127 assert_eq!(u8::MAX, 255); // 0..=255 } ``` 5. 🌟🌟 ```rust= // 解决代码中的错误和 `panic` fn main() { let v1 = 247_u8 + 8; let v2 = i8::checked_add(119, 8).unwrap(); println!("{},{}",v1,v2); } ``` 6. 🌟🌟 ```rust= // 修改 `assert!` 让代码工作 fn main() { let v = 1_024 + 0xff + 0o77 + 0b1111_1111; assert!(v == 1597); } ``` ### 浮点数 7. 🌟 ```rust= // 将 ? 替换成你的答案 fn main() { let x = 1_000.000_1; // f64 let y: f32 = 0.12; // f32 let z = 0.01_f64; // f64 } ``` 8. 🌟🌟 使用两种方法来让下面代码工作 ```rust= fn main() { assert!(0.1f32+0.2f32==0.3f32); } ``` ```rust= fn main() { assert!(0.3 - (0.1+0.2) < 0.000001); } ``` ### 序列Range 9. 🌟🌟 两个目标: 1. 修改 `assert!` 让它工作 2. 让 `println!` 输出: `97 - 122` ```rust= fn main() { let mut sum = 0; for i in -3..2 { sum += i } assert!(sum == -5); for c in 'a'..='z' { println!("{}",c as u8); } } ``` 10. 🌟🌟 ```rust= // 填空 use std::ops::{Range, RangeInclusive}; fn main() { assert_eq!((1..5), Range{ start: 1, end: 5 }); assert_eq!((1..=5), RangeInclusive::new(1, 5)); } ``` ### 计算 11. 🌟 ```rust= // 填空,并解决错误 fn main() { // 整数加法 assert!(1u32 + 2 == 3); // 整数减法 assert!(1i32 - 2 == -1); assert!(1i8 - 2 == -1); assert!(3 * 50 == 150); assert!(9 / 3 == 3); // error ! 修改它让代码工作 assert!(24 % 5 == 4); // 逻辑与或非操作 assert!(true && false == false); assert!(true || false == true); assert!(!true == false); // 位操作 println!("0011 AND 0101 is {:04b}", 0b0011u32 & 0b0101); println!("0011 OR 0101 is {:04b}", 0b0011u32 | 0b0101); println!("0011 XOR 0101 is {:04b}", 0b0011u32 ^ 0b0101); println!("1 << 5 is {}", 1u32 << 5); println!("0x80 >> 2 is 0x{:x}", 0x80u32 >> 2); } ``` ## 4.2 字符、布尔、单元类型 ### 字符 1. 🌟 ```rust= use std::mem::size_of_val; fn main() { let c1 = 'a'; assert_eq!(size_of_val(&c1), 4); let c2 = '中'; assert_eq!(size_of_val(&c2), 4); println!("Success!") } ``` 2. 🌟 ```rust= // 修改一行让代码正常打印 fn main() { let c1 = '中'; print_char(c1); } fn print_char(c: char) { println!("{}", c); } ``` ### 布尔 3. 🌟 ```rust= // 使成功打印 fn main() { let _f: bool = false; let t = true; if t { println!("Success!") } } ``` 4. 🌟 ```rust= fn main() { let f = false; let t = true && false; assert_eq!(t, f); println!("Success!") } ``` ### 单元类型 5. 🌟🌟 ```rust= // 让代码工作,但不要修改 `implicitly_ret_unit` ! fn main() { let v = (); assert_eq!(v, implicitly_ret_unit()); println!("Success!") } fn implicitly_ret_unit() { println!("I will return a ()") } // 不要使用下面的函数,它只用于演示! fn explicitly_ret_unit() -> () { println!("I will return a ()") } ``` 6. 🌟🌟 单元类型占用的内存大小是多少? ```rust= // 让代码工作:修改 `assert!` 中的 `4` use std::mem::size_of_val; fn main() { let unit: () = (); assert!(size_of_val(&unit) == 0); println!("Success!") } ``` ## 4.3 语句与表达式 1. 🌟🌟 ```rust= // 使用两种方法让代码工作起来 fn main() { let v = { let mut x = 1; x += 2; x }; assert_eq!(v, 3); } ``` ```rust= // 使用两种方法让代码工作起来 fn main() { let v = { let mut x = 1; x += 2; }; assert_eq!(v, ()); } ``` 2. 🌟 ```rust= fn main() { let v = { let x = 3; x }; assert!(v == 3); } ``` 3. 🌟 ```rust= fn main() { let s = sum(1, 2); assert_eq!(s, 3); } fn sum(x: i32, y: i32) -> i32 { x + y } ``` ## 4.4 函数 1. 🌟🌟🌟 ```rust= fn main() { // 不要修改下面两行代码! let (x, y) = (1, 2); let s = sum(x, y); assert_eq!(s, 3); } fn sum(x: i32, y: i32) -> i32 { x + y } ``` 2. 🌟🌟 ```rust= fn main() { print(); } // 使用另一个类型来替代 i32 fn print() -> () { println!("hello,world"); } ``` 3. 🌟🌟🌟 ```rust= // 用两种方法求解 fn main() { never_return(); } fn never_return() -> ! { // 实现这个函数,不要修改函数签名! panic!("I return nothing!") } ``` ```rust= // 用两种方法求解 fn main() { never_return(); } use::std::thread; use::std::time; fn never_return() -> ! { // 实现这个函数,不要修改函数签名! loop { println!("I return nothing!"); thread::sleep(time::Duration::from_secs(1)); } } ``` 4. 🌟🌟 发散函数( Diverging function )不会返回任何值,因此它们可以用于替代需要返回任何值的地方 ```rust= fn main() { println!("Success!"); } fn get_option(tp: u8) -> Option<i32> { match tp { 1 => { // TODO } _ => { // TODO } }; // 这里与其返回一个 None,不如使用发散函数替代 never_return_fn() } // 使用三种方法实现以下发散函数 fn never_return_fn() -> ! { todo!(); } ``` ```rust= // 使用三种方法实现以下发散函数 fn never_return_fn() -> ! { panic!() } ``` ```rust= // 使用三种方法实现以下发散函数 fn never_return_fn() -> ! { loop { std::thread::sleep(std::time::Duration::from_secs(1)) } } ``` 5. 🌟🌟 ```rust= fn main() { // 填空 let b = false; let _v = match b { true => 1, // 发散函数也可以用于 `match` 表达式,用于替代任何类型的值 false => { println!("Success!"); panic!("we have no value for `false`, but we can panic") } }; println!("Exercise Failed if printing out this line!"); } ``` # 5. 所有权和借用 ## 5.1 所有权 1. 🌟🌟 ```rust= fn main() { // 使用尽可能多的方法来通过编译 let x = String::from("hello, world"); let y = x.clone(); println!("{},{}",x,y); } ``` ```rust= fn main() { // 使用尽可能多的方法来通过编译 let x = String::from("hello, world"); let y = &x; println!("{},{}",x,y); } ``` ```rust= fn main() { // 使用尽可能多的方法来通过编译 let x = String::from("hello, world"); let y = String::from("hello, world"); println!("{},{}",x,y); } ``` 2. 🌟🌟 ```rust= // 不要修改 main 中的代码 fn main() { let s1 = String::from("hello, world"); let s2 = take_ownership(s1); println!("{}", s2); } // 只能修改下面的代码! fn take_ownership(s: String) -> String { println!("{}", &s); s } ``` 3. 🌟🌟 ```rust= fn main() { let s = give_ownership(); println!("{}", s); } // 只能修改下面的代码! fn give_ownership() -> String { let s = String::from("hello, world"); s } ``` 4. 🌟🌟 ```rust= // 修复错误,不要删除任何代码行 fn main() { let s = String::from("hello, world"); print_str(s.clone()); println!("{}", s); } fn print_str(s: String) { println!("{}",s) } ``` 5. 🌟🌟 ```rust= // 不要使用 clone,使用 copy 的方式替代 fn main() { let x = (1, 2, (), "hello"); let y = x; println!("{:?}, {:?}", x, y); } ``` ### 可变性 当所有权转移时,可变性也可以随之改变。 6. 🌟 ```rust= fn main() { let s = String::from("hello, "); // 只修改下面这行代码 ! let mut s1 = s; s1.push_str("world") } ``` 7. 🌟🌟🌟 ```rust= fn main() { let x = Box::new(5); let mut y = Box::new(3); // 完成该行代码,不要修改其它行! *y = 4; assert_eq!(*x, 5); } ``` ### 部分 move 当解构一个变量时,可以同时使用 `move` 和引用模式绑定的方式。当这么做时,部分 `move` 就会发生:变量中一部分的所有权被转移给其它变量,而另一部分我们获取了它的引用。 在这种情况下,原变量将无法再被使用,但是它没有转移所有权的那一部分依然可以使用,也就是之前被引用的那部分。 8. 🌟 ```rust= fn main() { let t = (String::from("hello"), String::from("world")); let _s = t.0; // 仅修改下面这行代码,且不要使用 `_s` println!("{:?}", t.1); } ``` 9. 🌟🌟 ```rust= fn main() { let t = (String::from("hello"), String::from("world")); // 填空,不要修改其它代码 let (ref s1, ref s2) = t; println!("{:?}, {:?}, {:?}", s1, s2, t); // -> "hello", "world", ("hello", "world") } ``` ## 5.2 引用和借用 ### 引用 1. 🌟 ```rust= fn main() { let x = 5; // 填写空白处 let p = &x; println!("x 的内存地址是 {:p}", p); // output: 0x16fa3ac84 } ``` 2. 🌟 ```rust= fn main() { let x = 5; let y = &x; // 只能修改以下行 assert_eq!(5, *y); } ``` 3. 🌟 ```rust= // 修复错误 fn main() { let mut s = String::from("hello, "); borrow_object(&s) } fn borrow_object(s: &String) {} ``` 4. 🌟 ```rust= // 修复错误 fn main() { let mut s = String::from("hello, "); push_str(&mut s) } fn push_str(s: &mut String) { s.push_str("world") } ``` 5. 🌟🌟 ```rust= fn main() { let mut s = String::from("hello, "); // 填写空白处,让代码工作 let p = &mut s; p.push_str("world"); } ``` ### ref `ref` 与 `&` 类似,可以用来获取一个值的引用,但是它们的用法有所不同。 6. 🌟🌟🌟 ```rust= fn main() { let c = '中'; let r1 = &c; // 填写空白处,但是不要修改其它行的代码 let ref r2 = c; assert_eq!(*r1, *r2); // 判断两个内存地址的字符串是否相等 assert_eq!(get_addr(r1),get_addr(r2)); } // 获取传入引用的内存地址的字符串形式 fn get_addr(r: &char) -> String { format!("{:p}", r) } ``` ### 借用规则 7. 🌟 ```rust= // 移除代码某个部分,让它工作 // 你不能移除整行的代码! fn main() { let mut s = String::from("hello"); let r1 = &s; let r2 = &s; println!("{}, {}", r1, r2); } ``` ### 可变性 8. 🌟 错误: 从不可变对象借用可变 ```rust= fn main() { // 通过修改下面一行代码来修复错误 let mut s = String::from("hello, "); borrow_object(&mut s) } fn borrow_object(s: &mut String) {} ``` 9. 🌟🌟 Ok: 从可变对象借用不可变 ```rust= // 下面的代码没有任何错误 fn main() { let mut s = String::from("hello, "); borrow_object(&s); s.push_str("world"); } fn borrow_object(s: &String) {} ``` ### NLL 10. 🌟🌟 ```rust= // 注释掉一行代码让它工作 fn main() { let mut s = String::from("hello, "); let r1 = &mut s; r1.push_str("world"); let r2 = &mut s; r2.push_str("!"); // println!("{}",r1); } ``` 11. 🌟🌟 ```rust= fn main() { let mut s = String::from("hello, "); let r1 = &mut s; let r2 = &mut s; // 在下面增加一行代码人为制造编译错误:cannot borrow `s` as mutable more than once at a time // 你不能同时使用 r1 和 r2 println!("get the value of s from r1: {}, r2: {}", *r1, *r2); } ``` # 6. 复合类型 ## 6.1 字符串 字符串字面量的类型是 `&str`, 例如 `let s: &str = "hello, world"` 中的 `"hello, world"` 的类型就是 `&str`。 ### str 和 &str 1. 🌟 正常情况下我们无法使用 `str` 类型,但是可以使用 `&str` 来替代 ```rust= // 修复错误,不要新增代码行 fn main() { let s: &str = "hello, world"; } ``` 2. 🌟🌟 如果要使用 `str` 类型,只能配合 `Box`。 `&` 可以用来将 `Box<str>` 转换为 `&str` 类型 ```rust= // 使用至少两种方法来修复错误 fn main() { let s: Box<str> = "hello, world".into(); greetings(&s) } fn greetings(s: &str) { println!("{}",s) } ``` ```rust= // 使用至少两种方法来修复错误 fn main() { let s: Box<&str> = "hello, world".into(); greetings(*s) } fn greetings(s: &str) { println!("{}", s) } ``` ### String `String` 是定义在标准库中的类型,分配在堆上,可以动态的增长。它的底层存储是动态字节数组的方式( `Vec<u8>` ),但是与字节数组不同,`String` 是 `UTF-8` 编码。 3. 🌟 ```rust= // 填空 fn main() { let mut s = String::new(); s.push_str("hello, world"); s.push('!'); assert_eq!(s, "hello, world!"); } ``` 4. 🌟🌟🌟 ```rust= // 修复所有错误,并且不要新增代码行 fn main() { let mut s = String::from("hello"); s.push(','); s.push_str(" world"); s += "!"; println!("{}", s) } ``` 5. 🌟🌟 我们可以用 replace 方法来替换指定的子字符串 ```rust= // 填空 fn main() { let s = String::from("I like dogs"); // 以下方法会重新分配一块内存空间,然后将修改后的字符串存在这里 let s1 = s.replace("dogs", "cats"); assert_eq!(s1, "I like cats") } ``` 6. 🌟🌟 你只能将 `String` 跟 `&str` 类型进行拼接,并且 `String` 的所有权在此过程中会被 `move` ```rust= // 修复所有错误,不要删除任何一行代码 fn main() { let s1 = String::from("hello,"); let s2 = String::from("world!"); let s3 = s1.clone() + &s2; assert_eq!(s3,"hello,world!"); println!("{}",s1); } ``` ### &str 和 String 与 `str` 的很少使用相比,`&str` 和 `String` 类型却非常常用,因此也非常重要。 7. 🌟🌟 我们可以使用两种方法将 `&str` 转换成 `String` 类型 ```rust= // 使用至少两种方法来修复错误 fn main() { let s = "hello, world".to_string(); greetings(s) } fn greetings(s: String) { println!("{}",s) } ``` ```rust= // 使用至少两种方法来修复错误 fn main() { let s = String::from("hello, world"); greetings(s) } fn greetings(s: String) { println!("{}",s) } ``` 8. 🌟🌟 我们可以使用 `String::from` 或 `to_string` 将 `&str` 转换成 `String` 类型 ```rust= // 使用两种方法来解决错误,不要新增代码行 fn main() { let s = "hello, world".to_string(); let s1: &str = &s; } ``` ```rust= // 使用两种方法来解决错误,不要新增代码行 fn main() { let s = "hello, world".to_string(); let s1: String = s; } ``` ### 字符串转义 9. 🌟 ```rust= fn main() { // 你可以使用转义的方式来输出想要的字符,这里我们使用十六进制的值,例如 \x73 会被转义成小写字母 's' // 填空以输出 "I'm writing Rust" let byte_escape = "I'm writing Ru\x73\x74!"; println!("What are you doing\x3F (\\x3F means ?) {}", byte_escape); // 也可以使用 Unicode 形式的转义字符 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); } ``` 10. 🌟🌟🌟 有时候需要转义的字符很多,我们会希望使用更方便的方式来书写字符串: `raw string`. ```rust= /* 填空并修复所有错误 */ fn main() { let raw_str = "Escapes don't work here: \x3F \u{211D}"; // 修改上面的行让代码工作 assert_eq!(raw_str, "Escapes don't work here: ? ℝ"); // 如果你希望在字符串中使用双引号,可以使用以下形式 let quotes = r#"And then I said: "There is no escape!""#; println!("{}", quotes); // 如果希望在字符串中使用 # 号,可以如下使用: let delimiter = r###"A string with "# in it. And even "##!"###; println!("{}", delimiter); // 填空 let long_delimiter = r###"Hello, "##""###; assert_eq!(long_delimiter, "Hello, \"##\"") } ``` ### 字节字符串 想要一个非 `UTF-8` 形式的字符串吗(我们之前的 `str`, `&str`, `String` 都是 `UTF-8` 字符串) ? 可以试试字节字符串或者说字节数组. ### 字符串索引string index 11. 🌟🌟 你无法通过索引的方式去访问字符串中的某个字符,但是可以使用切片的方式 `&s1[start..end]` ,但是 `start` 和 `end` 必须准确落在字符的边界处. ```rust= fn main() { let s1 = String::from("hi,中国"); let h = &s1[..1]; // 修改当前行来修复错误,提示: `h` 字符在 UTF-8 格式中只需要 1 个字节来表示 assert_eq!(h, "h"); let h1 = &s1[3..6];// 修改当前行来修复错误,提示: `中` 字符在 UTF-8 格式中需要 3 个字节来表示 assert_eq!(h1, "中"); } ``` ### 操作 UTF-8 字符串 12. 🌟 ```rust= fn main() { // 填空,打印出 "你好,世界" 中的每一个字符 for c in "你好,世界".chars() { println!("{}", c) } } ``` ## 6.2 数组 数组的类型是 `[T; Length]`,就如你所看到的,数组的长度是类型签名的一部分,因此数组的长度必须在编译期就已知,例如你不能使用以下方式来声明一个数组: ```rust= fn create_arr(n: i32) { let arr = [1; n]; } ``` 以上函数将报错,因为编译器无法在编译期知道 `n` 的具体大小。 1. 🌟 ```rust= fn main() { // 使用合适的类型填空 let arr: [i32; 5] = [1, 2, 3, 4, 5]; // 修改以下代码,让它顺利运行 assert!(arr.len() == 5); } ``` 2. 🌟🌟 ```rust= fn main() { // 很多时候,我们可以忽略数组的部分类型,也可以忽略全部类型,让编译器帮助我们推导 let arr0 = [1, 2, 3]; let arr: [_; 3] = ['a', 'b', 'c']; // 填空 // 数组分配在栈上, `std::mem::size_of_val` 函数会返回整个数组占用的内存空间 // 数组中的每个 char 元素占用 4 字节的内存空间,因为在 Rust 中, char 是 Unicode 字符 assert!(std::mem::size_of_val(&arr) == 12); } ``` 3. 🌟 数组中的所有元素可以一起初始化为同一个值 ```rust= fn main() { // 填空 let list: [i32; 100] = [1; 100] ; assert!(list[0] == 1); assert!(list.len() == 100); } ``` 4. 🌟 数组中的所有元素必须是同一类型 ```rust= fn main() { // 修复错误 let _arr = [1, 2, 3]; } ``` 5. 🌟 数组的下标索引从 0 开始. ```rust= fn main() { let arr = ['a', 'b', 'c']; let ele = arr[0]; // 只修改此行来让代码工作 assert!(ele == 'a'); } ``` 6. 🌟 越界索引会导致代码的 `panic`. ```rust= // 修复代码中的错误 fn main() { let names = [String::from("Sunfei"), "Sunface".to_string()]; // `get` 返回 `Option<T>` 类型,因此它的使用非常安全 let name0 = names.get(0).unwrap(); // 但是下标索引就存在越界的风险了 let _name1 = &names[1]; } ``` ## 6.3 切片( Slice ) 切片跟数组相似,但是切片的长度无法在编译期得知,因此你无法直接使用切片类型。 1. 🌟🌟 这里, `[i32]` 和 `str` 都是切片类型,但是直接使用它们会造成编译错误,如下代码所示。为了解决,你需要使用切片的引用: `&[i32]`,`&str`。 ```rust= // 修复代码中的错误,不要新增代码行! fn main() { let arr = [1, 2, 3]; let s1: &[i32] = &arr[0..2]; let s2: &str = "hello, world" as &str; } ``` 一个切片引用占用了 2 个字大小的内存空间( 从现在开始,为了简洁性考虑,如无特殊原因,我们统一使用切片来特指切片引用 )。 该切片的第一个字是指向数据的指针,第二个字是切片的长度。字的大小取决于处理器架构,例如在 `x86-64` 上,字的大小是 64 位也就是 8 个字节,那么一个切片引用就是 16 个字节大小。 切片( 引用 )可以用来借用数组的某个连续的部分,对应的签名是 `&[T]`,大家可以与数组的签名对比下 `[T; Length]`。 2. 🌟🌟🌟 ```rust= n main() { let arr: [char; 3] = ['中', '国', '人']; let slice = &arr[..2]; // 修改数字 `8` 让代码工作 // 小提示: 切片和数组不一样,它是引用。如果是数组的话,那下面的 `assert!` 将会通过: '中'和'国'是char类型,char类型是Unicode编码,大小固定为4字节,两个字符为8字节。 assert!(std::mem::size_of_val(&slice) == 16); } ``` 3. 🌟🌟 ```rust= fn main() { let arr: [i32; 5] = [1, 2, 3, 4, 5]; // 填空让代码工作起来 let slice: &[i32] = &arr[1..=3]; assert_eq!(slice, &[2, 3, 4]); } ``` ### 字符串切片 4. 🌟 ```rust= fn main() { let s = String::from("hello"); let slice1 = &s[0..2]; // 填空,不要再使用 0..2 let slice2 = &s[0..=1]; assert_eq!(slice1, slice2); } ``` 5. 🌟 ```rust= fn main() { let s = "你好,世界"; // 修改以下代码行,让代码工作起来 let slice = &s[0..3]; assert!(slice == "你"); } ``` 6. 🌟🌟 `&String` 可以被隐式地转换成 `&str` 类型. ```rust= // 修复所有错误 fn main() { let mut s = String::from("hello world"); // 这里, &s 是 `&String` 类型,但是 `first_character` 函数需要的是 `&str` 类型。 // 尽管两个类型不一样,但是代码仍然可以工作,原因是 `&String` 会被隐式地转换成 `&str` 类型,如果大家想要知道更多,可以看看 Deref 章节: https://course.rs/advance/smart-pointer/deref.html let ch = first_character(&s); println!("the first character is: {}", ch); s.clear(); } fn first_character(s: &str) -> &str { &s[..1] } ``` ## 6.4 元组( Tuple ) 1. 🌟 元组中的元素可以是不同的类型。元组的类型签名是 (T1, T2, ...), 这里 T1, T2 是相对应的元组成员的类型. ```rust= fn main() { let _t0: (u8,i16) = (0, -1); // 元组的成员还可以是一个元组 let _t1: (u8, (i16, u32)) = (0, (-1, 1)); // 填空让代码工作 let t: (u8, u16, i64, &str, String) = (1u8, 2u16, 3i64, "hello", String::from(", world")); } ``` 2. 🌟 可以使用索引来获取元组的成员 ```rust= // 修改合适的地方,让代码工作 fn main() { let t = ("i", "am", "sunface"); assert_eq!(t.2, "sunface"); } ``` 3. 🌟 过长的元组无法被打印输出 ```rust= // 修复代码错误 fn main() { let too_long_tuple = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); println!("too long tuple: {:?}", too_long_tuple); } ``` 4. 🌟 使用模式匹配来解构元组 ```rust= fn main() { let tup = (1, 6.4, "hello"); // 填空 let (x, z, y) = tup; assert_eq!(x, 1); assert_eq!(y, "hello"); assert_eq!(z, 6.4); } ``` 5. 🌟🌟 解构式赋值 ```rust= fn main() { let (x, y, z); // 填空 (y, z, x) = (1, 2, 3); assert_eq!(x, 3); assert_eq!(y, 1); assert_eq!(z, 2); } ``` 6. 🌟🌟 元组可以用于函数的参数和返回值 ```rust= fn main() { // 填空,需要稍微计算下 let (x, y) = sum_multiply((3, 2)); assert_eq!(x, 5); assert_eq!(y, 6); } fn sum_multiply(nums: (i32, i32)) -> (i32, i32) { (nums.0 + nums.1, nums.0 * nums.1) } ``` ## 6.5 结构体 ### 三种类型的结构体 1. 🌟 对于结构体,我们必须为其中的每一个字段都指定具体的值 ```rust= // fix the error struct Person { name: String, age: u8, hobby: String } fn main() { let age = 30; let p = Person { name: String::from("sunface"), age, hobby: String::from("Coding"), }; } ``` 2. 🌟 单元结构体没有任何字段。 ```rust= struct Unit; trait SomeTrait { // ...定义一些行为 } // 我们并不关心结构体中有什么数据( 字段 ),但我们关心它的行为。 // 因此这里我们使用没有任何字段的单元结构体,然后为它实现一些行为 impl SomeTrait for Unit { } fn main() { let u = Unit; do_something_with_unit(u); } // 填空,让代码工作 fn do_something_with_unit(u: Unit) { } ``` 3. 🌟🌟🌟 元组结构体看起来跟元组很像,但是它拥有一个结构体的名称,该名称可以赋予它一定的意义。由于它并不关心内部数据到底是什么名称,因此此时元组结构体就非常适合。 ```rust= // 填空并修复错误 struct Color(i32, i32, i32); struct Point(i32, i32, i32); fn main() { let v = Point(0, 127, 255); check_color(v); } fn check_color(p: Point) { let Point(x, _, _) = p; assert_eq!(x, 0); assert_eq!(p.1, 127); assert_eq!(p.2, 255); } ``` ### 结构体上的一些操作 4. 🌟 你可以在实例化一个结构体时将它整体标记为可变的,但是 Rust 不允许我们将结构体的某个字段专门指定为可变的. ```rust= // 填空并修复错误,不要增加或移除代码行 struct Person { name: String, age: u8, } fn main() { let age = 18; let mut p = Person { name: String::from("sunface"), age, }; // how can you believe sunface is only 18? p.age = 30; // 填空 p.name = String::from("sunfei"); } ``` 5. 🌟 使用结构体字段初始化缩略语法可以减少一些重复代码 ```rust= // 填空 struct Person { name: String, age: u8, } fn main() {} fn build_person(name: String, age: u8) -> Person { Person { age, name, } } ``` 6. 🌟 你可以使用结构体更新语法基于一个结构体实例来构造另一个 ```rust= // 填空,让代码工作 struct User { active: bool, username: String, email: String, sign_in_count: u64, } fn main() { let u1 = User { email: String::from("someone@example.com"), username: String::from("sunface"), active: true, sign_in_count: 1, }; let u2 = set_email(u1); } fn set_email(u: User) -> User { User { email: String::from("contact@im.dev"), ..u } } ``` ### 打印结构体 7. 🌟🌟 我们可以使用 `#[derive(Debug)]` 让结构体变成可打印的. ```rust= // 填空,让代码工作 #[derive(Debug)] struct Rectangle { width: u32, height: u32, } fn main() { let scale = 2; let rect1 = Rectangle { width: dbg!(30 * scale), // 打印 debug 信息到标准错误输出 stderr,并将 `30 * scale` 的值赋给 `width` height: 50, }; dbg!(&rect1); // 打印 debug 信息到标准错误输出 stderr println!("{:#?}", rect1); // 打印 debug 信息到标准输出 stdout } ``` ### 结构体的所有权 当解构一个变量时,可以同时使用 `move` 和引用模式绑定的方式。当这么做时,部分 `move` 就会发生:变量中一部分的所有权被转移给其它变量,而另一部分我们获取了它的引用。 在这种情况下,原变量将无法再被使用,但是它没有转移所有权的那一部分依然可以使用,也就是之前被引用的那部分。 8. 🌟🌟 ```rust= // 修复错误 #[derive(Debug)] struct File { name: String, data: String, } fn main() { let f = File { name: String::from("readme.md"), data: "Rust By Practice".to_string() }; let _name = f.name; // 只能修改这一行 println!("{}, {}", _name, f.data); } ``` ## 6.6 枚举 Enum > OS:這個章節的練習跟 Rust Course 的教學內容差太多了吧…嚴重超綱 1. 🌟🌟 在创建枚举时,你可以使用显式的整数设定枚举成员的值。 ```rust= // 修复错误 enum Number { Zero, One, Two, } enum Number1 { Zero = 0, One, Two, } // C语言风格的枚举定义 enum Number2 { Zero = 0, One = 1, Two = 2, } fn main() { // 通过 `as` 可以将枚举值强转为整数类型 assert_eq!(Number::One as i32, Number1::One as i32); assert_eq!(Number1::One as i32, Number2::One as i32); } ``` 2. 🌟 枚举成员可以持有各种类型的值 ```rust= // 填空 enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), } fn main() { let msg1 = Message::Move{x: 1, y: 2}; // 使用x = 1, y = 2 来初始化 let msg2 = Message::Write(String::from("hello, world!")); // 使用 "hello, world!" 来初始化 } ``` 3. 🌟🌟 枚举成员中的值可以使用模式匹配来获取 > OS:這題有問題吧…連解答給的都不是「僅填空」… ```rust= // 仅填空并修复错误 enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), } fn main() { let msg = Message::Move{x: 1, y: 1}; if let Message::Move{x: a, y: b} = msg { assert_eq!(a, b); } else { panic!("不要让这行代码运行!"); } } ``` 4. 🌟🌟 使用枚举对类型进行同一化 ```rust= // 填空,并修复错误 #[derive(Debug)] enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), } fn main() { let msgs: [Message; 3] = [ Message::Quit, Message::Move{x:1, y:3}, Message::ChangeColor(255,255,0) ]; for msg in msgs { show_message(msg) } } fn show_message(msg: Message) { println!("{:#?}", msg); } ``` 5. 🌟🌟 Rust 中没有 `null`,我们通过 `Option<T>` 枚举来处理值为空的情况 ```rust= // 填空让 `println` 输出,同时添加一些代码不要让最后一行的 `panic` 执行到 fn main() { let five = Some(5); let six = plus_one(five); let none = plus_one(None); if let Some(n) = six { println!("{}", n); return } panic!("不要让这行代码运行!"); } fn plus_one(x: Option<i32>) -> Option<i32> { match x { None => None, Some(i) => Some(i + 1), } } ``` 6. 🌟🌟🌟🌟 使用枚举来实现链表. > OS:…我不知道要說什麼了,教程第一章才叫我們在熟悉語法前不要試著實現鏈表… ```rust= // 填空,让代码运行 use crate::List::*; enum List { // Cons: 链表中包含有值的节点,节点是元组类型,第一个元素是节点的值,第二个元素是指向下一个节点的指针 Cons(u32, Box<List>), // Nil: 链表中的最后一个节点,用于说明链表的结束 Nil, } // 为枚举实现一些方法 impl List { // 创建空的链表 fn new() -> List { // 因为没有节点,所以直接返回 Nil 节点 // 枚举成员 Nil 的类型是 List Nil } // 在老的链表前面新增一个节点,并返回新的链表 fn prepend(self, elem: u32) -> List { Cons(elem, Box::new(self)) } // 返回链表的长度 fn len(&self) -> u32 { match *self { // 这里我们不能拿走 tail 的所有权,因此需要获取它的引用 Cons(_, ref tail) => 1 + tail.len(), // 空链表的长度为 0 Nil => 0 } } // 返回链表的字符串表现形式,用于打印输出 fn stringify(&self) -> String { match *self { Cons(head, ref tail) => { // 递归生成字符串 format!("{}, {}", head, tail.stringify()) }, Nil => { format!("Nil") }, } } } fn main() { // 创建一个新的链表(也是空的) let mut list = List::new(); // 添加一些元素 list = list.prepend(1); list = list.prepend(2); list = list.prepend(3); // 打印列表的当前状态 println!("链表的长度是: {}", list.len()); println!("{}", list.stringify()); } ``` > 結果還是寫出來了,耶 # 7. 流程控制 ### if/else 1. 🌟 ```rust= // 填空 fn main() { let n = 5; if n < 0 { println!("{} is negative", n); } else if n > 0 { println!("{} is positive", n); } else { println!("{} is zero", n); } } ``` 2. 🌟🌟 if/else 可以用作表达式来进行赋值 ```rust= // 修复错误 fn main() { let n: f64 = 5.0; let big_n = if n < 10.0 || n > -10.0 { println!(" 数字太小,先增加 10 倍再说"); 10.0 * n } else { println!("数字太大,我们得让它减半"); n / 2.0 }; println!("{} -> {}", n, big_n); } ``` ### for 3. 🌟 `for in` 可以用于迭代一个迭代器,例如序列 `a..b`. ```rust= fn main() { for n in 1..100 { // 修改此行,让代码工作 if n == 100 { panic!("NEVER LET THIS RUN") } } } ``` 4. 🌟🌟 ```rust= // 修复错误,不要新增或删除代码行 fn main() { let names = [String::from("liming"),String::from("hanmeimei")]; for name in &names { // do something with name... } println!("{:?}", names); let numbers = [1, 2, 3]; // numbers中的元素实现了 Copy,因此无需转移所有权 for n in numbers { // do something with name... } println!("{:?}", numbers); } ``` 5. 🌟 ```rust= fn main() { let a = [4,3,2,1]; // 通过索引和值的方式迭代数组 `a` for (i,v) in a.iter().enumerate() { println!("第{}个元素是{}",i+1,v); } } ``` ### while 6. 🌟🌟 当条件为 `true` 时,`while` 将一直循环 ```rust= // 填空,让最后一行的 println! 工作 ! fn main() { // 一个计数值 let mut n = 1; // 当条件为真时,不停的循环 while n < 10 { if n % 15 == 0 { println!("fizzbuzz"); } else if n % 3 == 0 { println!("fizz"); } else if n % 5 == 0 { println!("buzz"); } else { println!("{}", n); } n += 1; } println!("n 的值是 {}, 循环结束",n); } ``` ### continue and break 7. 🌟 使用 `break` 可以跳出循环 ```rust= // 填空,不要修改其它代码 fn main() { let mut n = 0; for i in 0..=100 { if n == 66 { break; } n += 1; } assert_eq!(n, 66); } ``` 8. 🌟🌟 `continue` 会结束当次循环并立即开始下一次循环 ```rust= // 填空,不要修改其它代码 fn main() { let mut n = 0; for i in 0..=100 { if n != 66 { n+=1; continue; } break; } assert_eq!(n, 66); } ``` ### loop 9. 🌟🌟 `loop` 一般都需要配合 `break` 或 `continue` 一起使用。 ```rust= // 填空,不要修改其它代码 fn main() { let mut count = 0u32; println!("Let's count until infinity!"); // 无限循环 loop { count += 1; if count == 3 { println!("three"); // 跳过当此循环的剩余代码 continue; } println!("{}", count); if count == 5 { println!("OK, that's enough"); break;; } } assert_eq!(count, 5); } ``` 10. 🌟🌟 `loop` 是一个表达式,因此我们可以配合 `break` 来返回一个值 ```rust= // 填空 fn main() { let mut counter = 0; let result = loop { counter += 1; if counter == 10 { break counter * 2; } }; assert_eq!(result, 20); } ``` 11. 🌟🌟🌟 当有多层循环时,你可以使用 `continue` 或 `break` 来控制外层的循环。要实现这一点,外部的循环必须拥有一个标签 `'label`, 然后在 `break` 或 `continue` 时指定该标签 > 太酷了吧還有這種功能 ```rust= // 填空 fn main() { let mut count = 0; 'outer: loop { 'inner1: loop { if count >= 20 { // 这只会跳出 inner1 循环 break 'inner1; // 这里使用 `break` 也是一样的 } count += 2; } count += 5; 'inner2: loop { if count >= 30 { break 'outer; } continue 'outer; } } assert!(count == 30) } ``` # 8. 模式匹配 ## 8.1. match, matches! 和 if let ### match 1. 🌟🌟 ```rust= // 填空 enum Direction { East, West, North, South, } fn main() { let dire = Direction::South; match dire { Direction::East => println!("East"), Direction::South | Direction::North => { // 在这里匹配 South 或 North println!("South or North"); }, _ => println!("Other Direction (West)"), }; } ``` 2. 🌟🌟 match 是一个表达式,因此可以用在赋值语句中 ```rust= fn main() { let boolean = true; // 使用 match 表达式填空,并满足以下条件 // // boolean = true => binary = 1 // boolean = false => binary = 0 let binary = match boolean { true => 1, false => 0 }; assert_eq!(binary, 1); } ``` 3. 🌟🌟 使用 match 匹配出枚举成员持有的值 ```rust= // 填空 enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), } fn main() { let msgs = [ Message::Quit, Message::Move{x:1, y:3}, Message::ChangeColor(255,255,0) ]; for msg in msgs { show_message(msg) } } fn show_message(msg: Message) { match msg { Message::Move { x: a, y: b } => { // 这里匹配 Message::Move assert_eq!(a, 1); assert_eq!(b, 3); }, Message::ChangeColor(_, g, b) => { assert_eq!(g, 255); assert_eq!(b, 0); } __ => println!("no data in these variants") } } ``` 我覺得這題官方 Github 給的 Solution 看起來有點奇怪,改這樣可能比較好: ```rust= // 填空 enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), } fn main() { let msgs = [ Message::Quit, Message::Move{x:1, y:3}, Message::Write(String::from("hello")), Message::ChangeColor(255,255,0) ]; for msg in msgs { show_message(msg) } } fn show_message(msg: Message) { match msg { Message::Move { x: a, y: b } => { // 这里匹配 Message::Move assert_eq!(a, 1); assert_eq!(b, 3); println!("Move match!"); }, Message::Write(s) => { assert_eq!(s, String::from("hello")); println!("Write match!"); }, Message::ChangeColor(_, g, b) => { assert_eq!(g, 255); assert_eq!(b, 0); println!("ChangeColor match!"); } __ => println!("no data in these variants") } } ``` ### matches! `matches!` 看起来像 `match`, 但是它可以做一些特别的事情 4. 🌟🌟 ```rust= fn main() { let alphabets = ['a', 'E', 'Z', '0', 'x', '9' , 'Y']; // 使用 `matches` 填空 for ab in alphabets { assert!(matches!(ab, alphabets)); } } ``` 5. 🌟🌟 ```rust= enum MyEnum { Foo, Bar } fn main() { let mut count = 0; let v = vec![MyEnum::Foo,MyEnum::Bar,MyEnum::Foo]; for e in v { if matches!(e, MyEnum::Foo) { // 修复错误,只能修改本行代码 count += 1; } } assert_eq!(count, 2); } ``` ### if let 在有些时候, 使用 `match` 匹配枚举有些太重了,此时 `if let` 就非常适合. 6. 🌟 ```rust= fn main() { let o = Some(7); // 移除整个 `match` 语句块,使用 `if let` 替代 if let Some(i) = o { println!("This is a really long string and `{:?}`", i); } // match o { // Some(i) => { // println!("This is a really long string and `{:?}`", i); // } // _ => {} // }; } ``` 7. 🌟🌟 ```rust= // 填空 enum Foo { Bar(u8) } fn main() { let a = Foo::Bar(1); if let Foo::Bar(i) = a { println!("foobar 持有的值是: {}", i); } } ``` 8. 🌟🌟 ```rust= enum Foo { Bar, Baz, Qux(u32) } fn main() { let a = Foo::Qux(10); match a { Foo::Bar => println!("match foo::bar"), Foo::Baz => println!("match foo::baz"), _ => println!("match others") }; // // 移除以下代码,使用 `match` 代替 // if let Foo::Bar = a { // println!("match foo::bar") // } else if let Foo::Baz = a { // println!("match foo::baz") // } else { // println!("match others") // } } ``` ### 变量遮蔽( Shadowing ) 9. 🌟🌟 ```rust= // 就地修复错误 fn main() { let age = Some(30); if let Some(age) = age { // 创建一个新的变量,该变量与之前的 `age` 变量同名 assert_eq!(age, 30); } // 新的 `age` 变量在这里超出作用域 match age { // `match` 也能实现变量遮蔽 Some(age) => println!("age 是一个新的变量,它的值是 {}",age), _ => () } } ``` ## 8.2 模式 1. 🌟🌟 使用 `|` 可以匹配多个值, 而使用 `..=` 可以匹配一个闭区间的数值序列 ```rust= fn main() {} fn match_number(n: i32) { match n { // 匹配一个单独的值 1 => println!("One!"), // 使用 `|` 填空,不要使用 `..` 或 `..=` 2 | 3 | 4 | 5 => println!("match 2 -> 5"), // 匹配一个闭区间的数值序列 6..=10 => { println!("match 6 -> 10") }, 11.. => { println!("match 11 -> +infinite") } _ => { println!("Others") } } } ``` 這題我附的是自已的解法,除了 `11..` 代表 `match 11 -> +infinite` 以外,還加了 `_` 來代表其它分支。 Github 解法寫的是: ```rust= _ => { println!("match 11 -> +infinite") } ``` 但是這個函數的 input 是 i32,而不是 u32 類型,所以負數的部分不應該印出 `match 11 -> +infinite`。解答看起來怪怪的。 2. 🌟🌟🌟 @ 操作符可以让我们将一个与模式相匹配的值绑定到新的变量上 ```rust= struct Point { x: i32, y: i32, } fn main() { // 填空,让 p 匹配第二个分支 let p = Point { x: 0, y: 20 }; match p { Point { x, y: 0 } => println!("On the x axis at {}", x), // 第二个分支 Point { x: 0..=5, y: y@ (10 | 20 | 30) } => println!("On the y axis at {}", y), Point { x, y } => println!("On neither axis: ({}, {})", x, y), } } ``` 3. 🌟🌟🌟 ```rust= // 修复错误 enum Message { Hello { id: i32 }, } fn main() { let msg = Message::Hello { id: 5 }; match msg { Message::Hello { id: id@3..=7, } => println!("id 值的范围在 [3, 7] 之间: {}", id), Message::Hello { id: newid@ (10 | 11 | 12) } => { println!("id 值的范围在 [10, 12] 之间: {}", newid) } Message::Hello { id } => println!("Found some other id: {}", id), } } ``` 4. 🌟🌟 匹配守卫(match guard)是一个位于 match 分支模式之后的额外 if 条件,它能为分支模式提供更进一步的匹配条件。 ```rust= // 填空让代码工作,必须使用 `split` fn main() { let num = Some(4); let split = 5; match num { Some(x) if x < split => assert!(x < split), Some(x) => assert!(x >= split), None => (), } } ``` 5. 🌟🌟🌟 使用 `..` 忽略一部分值 ```rust= // 填空,让代码工作 fn main() { let numbers = (2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048); match numbers { (first, .., last) => { assert_eq!(first, 2); assert_eq!(last, 2048); } } } ``` 6. 🌟🌟 使用模式 `&mut V` 去匹配一个可变引用时,你需要格外小心,因为匹配出来的 `V` 是一个值,而不是可变引用 這題太難了,已經牽涉 pattern matching 的底層實現… # 9. 方法和关联函数 示例 ```rust= struct Point { x: f64, y: f64, } // `Point` 的关联函数都放在下面的 `impl` 语句块中 impl Point { // 关联函数的使用方法跟构造器非常类似 fn origin() -> Point { Point { x: 0.0, y: 0.0 } } // 另外一个关联函数,有两个参数 fn new(x: f64, y: f64) -> Point { Point { x: x, y: y } } } struct Rectangle { p1: Point, p2: Point, } impl Rectangle { // 这是一个方法 // `&self` 是 `self: &Self` 的语法糖 // `Self` 是当前调用对象的类型,对于本例来说 `Self` = `Rectangle` fn area(&self) -> f64 { // 使用点操作符可以访问 `self` 中的结构体字段 let Point { x: x1, y: y1 } = self.p1; let Point { x: x2, y: y2 } = self.p2; // `abs` 是一个 `f64` 类型的方法,会返回调用者的绝对值 ((x1 - x2) * (y1 - y2)).abs() } fn perimeter(&self) -> f64 { let Point { x: x1, y: y1 } = self.p1; let Point { x: x2, y: y2 } = self.p2; 2.0 * ((x1 - x2).abs() + (y1 - y2).abs()) } // 该方法要求调用者是可变的,`&mut self` 是 `self: &mut Self` 的语法糖 fn translate(&mut self, x: f64, y: f64) { self.p1.x += x; self.p2.x += x; self.p1.y += y; self.p2.y += y; } } // `Pair` 持有两个分配在堆上的整数 struct Pair(Box<i32>, Box<i32>); impl Pair { // 该方法会拿走调用者的所有权 // `self` 是 `self: Self` 的语法糖 fn destroy(self) { let Pair(first, second) = self; println!("Destroying Pair({}, {})", first, second); // `first` 和 `second` 在这里超出作用域并被释放 } } fn main() { let rectangle = Rectangle { // 关联函数的调用不是通过点操作符,而是使用 `::` p1: Point::origin(), p2: Point::new(3.0, 4.0), }; // 方法才是通过点操作符调用 // 注意,这里的方法需要的是 `&self` 但是我们并没有使用 `(&rectangle).perimeter()` 来调用,原因在于: // 编译器会帮我们自动取引用 // `rectangle.perimeter()` === `Rectangle::perimeter(&rectangle)` println!("Rectangle perimeter: {}", rectangle.perimeter()); println!("Rectangle area: {}", rectangle.area()); let mut square = Rectangle { p1: Point::origin(), p2: Point::new(1.0, 1.0), }; // 错误!`rectangle` 是不可变的,但是这个方法要求一个可变的对象 //rectangle.translate(1.0, 0.0); // TODO ^ 试着反注释此行,看看会发生什么 // 可以!可变对象可以调用可变的方法 square.translate(1.0, 1.0); let pair = Pair(Box::new(1), Box::new(2)); pair.destroy(); // Error! 上一个 `destroy` 调用拿走了 `pair` 的所有权 //pair.destroy(); // TODO ^ 试着反注释此行 } ``` ### Method 1. 🌟🌟 方法跟函数类似:都是使用 `fn` 声明,有参数和返回值。但是与函数不同的是,方法定义在结构体的上下文中(枚举、特征对象也可以定义方法),而且方法的第一个参数一定是 `self` 或其变体 `&self` 、`&mut self`,`self` 代表了当前调用的结构体实例。 ```rust= struct Rectangle { width: u32, height: u32, } impl Rectangle { // 完成 area 方法,返回矩形 Rectangle 的面积 fn area(&self) -> u32 { return self.width * self.height; } } fn main() { let rect1 = Rectangle { width: 30, height: 50 }; assert_eq!(rect1.area(), 1500); } ``` 2. 🌟🌟 `self` 会拿走当前结构体实例(调用对象)的所有权,而 `&self` 却只会借用一个不可变引用,`&mut self` 会借用一个可变引用 ```rust= // 只填空,不要删除任何代码行! #[derive(Debug)] struct TrafficLight { color: String, } impl TrafficLight { pub fn show_state(&self) { println!("the current state is {}", self.color); } } fn main() { let light = TrafficLight{ color: "red".to_owned(), }; // 不要拿走 `light` 的所有权 light.show_state(); // 否则下面代码会报错 println!("{:?}", light); } ``` 3. 🌟🌟 `&self` 实际上是 `self: &Self` 的缩写或者说语法糖 ```rust= struct TrafficLight { color: String, } impl TrafficLight { // 使用 `Self` 填空 pub fn show_state(self: &Self) { println!("the current state is {}", self.color); } // 填空,不要使用 `Self` 或其变体 pub fn change_state(&mut self) { self.color = "green".to_string() } } fn main() {} ``` ### Associated function 4. 🌟🌟 定义在 `impl` 语句块中的函数被称为关联函数,因为它们跟当前类型关联在一起。关联函数与方法最大的区别就是它第一个参数不是 `self` ,原因是它们不需要使用当前的实例,因此关联函数往往可以用于构造函数:初始化一个实例对象。 ```rust= #[derive(Debug)] struct TrafficLight { color: String, } impl TrafficLight { // 1. 实现下面的关联函数 `new`, // 2. 该函数返回一个 TrafficLight 实例,包含 `color` "red" // 3. 该函数必须使用 `Self` 作为类型,不能在签名或者函数体中使用 `TrafficLight` pub fn new() -> Self { TrafficLight { color: "red".to_string() } } pub fn get_state(&self) -> &str { &self.color } } fn main() { let light = TrafficLight::new(); assert_eq!(light.get_state(), "red"); } ``` ### 多个 impl 语句块 5. 🌟 每一个结构体允许拥有多个 `impl` 语句块 ```rust= struct Rectangle { width: u32, height: u32, } // 使用多个 `impl` 语句块重写下面的代码 impl Rectangle { fn area(&self) -> u32 { self.width * self.height } } impl Rectangle { fn can_hold(&self, other: &Rectangle) -> bool { self.width > other.width && self.height > other.height } } fn main() {} ``` ### Enums 6. 🌟🌟🌟 我们还可以为枚举类型定义方法 ```rust= #[derive(Debug)] enum TrafficLightColor { Red, Yellow, Green, } // 为 TrafficLightColor 实现所需的方法 impl TrafficLightColor { fn color(&self) -> String { match self { Self::Red => "red".to_string(), Self::Yellow => "yellow".to_string(), Self::Green => "green".to_string() } } } fn main() { let c = TrafficLightColor::Yellow; assert_eq!(c.color(), "yellow"); println!("{:?}",c); } ``` # 10. 泛型 ### 函数