# 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. 泛型
### 函数