陪老 K 学 Rust (七)
元组可以说是 Rust 最简单的自定类型。通过元组来理解 模式匹配
和 解构
-现代语言的时尚特性。
解构 (destruct) 是指把值从某些解构中提取出来,并且绑定到新的变量上。为了更好得理解解构,我们写一个辅助性的函数 print_type_of
来打印变量的类型,并且使用最简单的 元组
来进行演示.
fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>());
}
1 解构的基本形式
fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>());
}
fn main() {
let (x, y, z) = (0, 1, 2);
print_type_of(&x);
print_type_of(&y);
print_type_of(&z);
println!("x:{}, y:{}, z:{}", x, y, z);
}
运行输出:
i32
i32
i32
x:0, y:1, z:2
不关心的解构字段用 _
或者 ..
进行占位,如:
fn main() {
let (x, _, z) = (0, 1, 2);
println!("x:{}, z:{}", x, z);
let (x, ..) = (3, 4, 5);
println!("x:{}", x);
let (.., z) = (6, 7, 8, 9);
println!("z:{}", z);
let (.., y, z) = (10, 11, 12, 13, 14);
println!("y: {}, z:{}", y, z);
}
运行输出:
x:0, z:2
x:3
z:9
y: 13, z:1
解构要诀:
- 使用
let
,=
左右两边的类型一致。- 使用
_
作为占位符忽略 1 个匹配字段。- 使用
..
作为占位符忽略首尾多个字段。- 在一个解构中不能使用多个
..
进行字段忽略,因为有语义歧义。
由于变量数量不足而引起的类型不匹配:
fn main() {
let (x, y) = (0, 1, 2);
println!("x:{}, y:{}", x, y);
}
编译报错:
error[E0308]: mismatched types
--> r28.rs:2:9
|
2 | let (x, y) = (0, 1, 2);
| ^^^^^^ expected a tuple with 3 elements, found one with 2 elements
|
= note: expected type `({integer}, {integer}, {integer})`
found type `(_, _)`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0308`
有些编程语言在解构变量数量不足时,最后一个变量会解构所有剩余的元组元素,从而变成一个元组,但是 Rust 不会。
2 解构出可变绑定
fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>());
}
fn main() {
let (x, mut y, z) = (0, 1, 2);
print_type_of(&x);
print_type_of(&y);
print_type_of(&z);
println!("x:{}, y:{}, z:{}", x, y, z);
y = 4;
println!("{:}", y);
}
运行输出
i32
i32
i32
x:0, y:1, z:2
4
3 解构出引用
fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>());
}
fn main() {
let (x, *y, z) = (0, 1, 2);
print_type_of(&x);
print_type_of(&y);
print_type_of(&z);
println!("x:{}, y:{}, z:{}", x, y, z);
}
我们妄图使用 *y = i32
的形式解构出一个 &i32
, 编译器报错:
error: expected pattern, found `*`
--> r28.rs:2:13
|
2 | let (x, *y, z) = (0, 1, 2);
| ^ expected pattern
error: aborting due to previous error
???, 原来想解构出 引用
的语法形式是 ref
, 为什么 不是 *x
的形式?
fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>());
}
fn main() {
let (x, ref y, z) = (0, 1, 2);
print_type_of(&x);
print_type_of(&y);
print_type_of(&z);
println!("x:{}, y:{}, z:{}", x, y, z);
}
运行输出:
i32
&i32
i32
x:0, y:1, z:2
如果要解构出一个 ref mut
呢?
fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>());
}
fn main() {
let (x, ref mut y, z) = (0, 1, 2);
print_type_of(&x);
print_type_of(&y);
print_type_of(&z);
println!("x:{}, y:{}, z:{}", x, y, z);
}
运行输出:
i32
&mut i32
i32
x:0, y:1, z:
如果要解构出一个 mut ref
呢?
fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>());
}
fn main() {
let (x, mut ref y, z) = (0, 1, 2);
print_type_of(&x);
print_type_of(&y);
print_type_of(&z);
println!("x:{}, y:{}, z:{}", x, y, z);
}
不要太想当然 :(, 编译器报错。
error: the order of `mut` and `ref` is incorrect
--> r29.rs:6:13
|
6 | let (x, mut ref y, z) = (0, 1, 2);
| ^^^^^^^ help: try switching the order: `ref mut`
error: aborting due to previous error
4 嵌套解构
当然解构也可以嵌套的,也就是可以解构出内层的元组的元素。
fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>());
}
fn main() {
let tuple = (1, 2, 3, 4, (10, 11, 12, 13));
let (.., (x,..)) = tuple;
println!("x: {}", x);
print_type_of(&x);
}
运行输出:
x: 10
i3
5 绑定与解构
从形式的一致性来说: let p = &mut x;
这种绑定也符合 解构
的一般形式。
fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>());
}
fn main() {
let mut v = 10;
print_type_of(&v);
let p = &v;
print_type_of(&p);
let ref p = v;
print_type_of(&p);
let ref mut p = v;
print_type_of(&p);
}
运行输出:
i32
&i32
&i32
&mut i32
6 解构与生命周期
假设一个元组由数个元素组成,如果进行解构的话,其所有权是否会被转移?答案是 会, 看代码:
#[derive(Debug)]
struct Foobar(i32);
impl Drop for Foobar {
fn drop (&mut self) {
println!("Dropping {:?}", self);
}
}
fn main() {
let x = (Foobar(0), );
let (foobar,) = x;
println!("{:?}", foobar);
println!("{:?}", x);
}
编译器报错:x 的所有权已经被转移。
error[E0382]: borrow of moved value: `x`
--> r33.rs:14:22
|
12 | let (foobar,) = x;
| ------ value moved here
13 | println!("{:?}", foobar);
14 | println!("{:?}", x);
| ^ value borrowed here after partial move
|
= note: move occurs because `x.0` has type `Foobar`, which does not implement the `Copy` trait
error: aborting due to previous error
For more information about this error, try `rustc --explain E0382`.
如果使用引用解构的话,则不会,符合生命周期的心智模型。
#[derive(Debug)]
struct Foobar(i32);
impl Drop for Foobar {
fn drop (&mut self) {
println!("Dropping {:?}", self);
}
}
fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>());
}
fn main() {
let x = (Foobar(0), );
let (ref foobar,) = x;
println!("{:?}", foobar);
println!("{:?}", x);
print_type_of(&x);
print_type_of(&foobar);
}
Foobar(0)
(Foobar(0),)
(r34::Foobar,)
&r34::Foobar
Dropping Foobar(0)
7 模式匹配与解构
除了解构之外,元组还可以在使用 match
关键字进行模式匹配的同时进行解构。
fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>());
}
fn main() {
let v = (0, 3);
match v {
(x, 1) => {
print_type_of(&x);
println!("match (x, 1)");
},
(x, 2) => {
print_type_of(&x);
println!("match (x, 2)");
},
(x, 3) => {
print_type_of(&x);
println!("match (x, 3)");
},
_ => {
println!("not match any");
}
}
}
i32
match (x, 3)
注意使用
_
进行默认匹配来全覆盖匹配的所有分支。
fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>());
}
fn main() {
let v = (0, 3);
match &v {
(x, 1) => {
print_type_of(&x);
println!("match (x, 1)");
},
(x, 2) => {
print_type_of(&x);
println!("match (x, 2)");
},
(x, 3) => {
print_type_of(&x);
println!("match (x, 3)");
},
_ => {
println!("not match any");
}
}
}
&i32
match (x, 3)
使用 ref
来引用解构。
fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>());
}
fn main() {
let v = (0, 3);
match v {
(ref x, 1) => {
print_type_of(&x);
println!("match (x, 1)");
},
(ref x, 2) => {
print_type_of(&x);
println!("match (x, 2)");
},
(ref x, 3) => {
print_type_of(&x);
println!("match (x, 3)");
},
_ => {
println!("not match any");
}
}
}
&i32
match (x, 3)
注意以上两段代码的不同点。
如果要解构可变引用呢?
fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>());
}
fn main() {
let mut v = (0, 3);
match v {
(ref mut x, 1) => {
print_type_of(&x);
println!("match (x, 1)");
},
(ref mut x, 2) => {
print_type_of(&x);
println!("match (x, 2)");
},
(ref mut x, 3) => {
print_type_of(&x);
println!("match (x, 3)");
},
_ => {
println!("not match any");
}
}
}
&mut i32
match (x, 3)
8 模式匹配与条件解构
fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>());
}
fn main() {
let v = (0, 1);
match v {
(x, y) if y > 0 && y <= 2 => {
print_type_of(&x);
println!("match (x, y >0 && y <=2)");
},
(x, 1) => {
print_type_of(&x);
println!("match (x, 1)");
},
(x, 3) => {
print_type_of(&x);
println!("match (x, 3)");
},
_ => {
println!("not match any");
}
}
}
运行输出
i32
match (x, y >0 && y <=2)
- 使用
if
来限定解构条件,本例子中是if y> 0 && y<=2
.- 模式匹配是从上到下进行匹配测试的,一旦满足测试条件,则不再进行匹配测试。本例子中的
(0, 1)
虽然满足前两个测试分支,但是(x, 1)
匹配分支不会被执行。- 由于语义的限制,条件解构需要使用
_
来达到全覆盖的效果。
9 模式匹配与绑定
在模式匹配情况下,也可以使用 @
在匹配的同时绑定。
fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>());
}
fn main() {
let v = (0, 1);
match v {
(x, y @ 0..=2) => {
print_type_of(&x);
print_type_of(&y);
println!("y is: {}", y);
println!("match (x, y @ 0 ..= 2)");
},
(x, 1) => {
print_type_of(&x);
println!("match (x, 1)");
},
(x, 3) => {
print_type_of(&x);
println!("match (x, 3)");
},
_ => {
println!("not match any");
}
}
}
运行输出:
warning: unreachable pattern
--> r39.rs:14:9
|
14 | (x, 1) => {
| ^^^^^^
|
= note: `#[warn(unreachable_patterns)]` on by default
i32
i32
y is: 0
match (x, y @ 0 ..= 2)
看上去 @
和 if
区别不大,实际上,在某些情况下, @
是非常有用的:考虑我们
match
的不是某个变量,而是某个函数的返回值,在满足条件的情况下,我们需要绑定这个函数的返回值进行某些操作,而其他情况下,我们不使用它,也就没有必要绑定它。而且使用 @
要比 if
简洁。
fn calculate_score() -> i32 {
100
}
fn main() {
match calculate_score() {
0 ..= 59 => {
println!("bad");
},
score @ 60..=100 => {
println!("good, my score is: {}", score);
},
_ => {
println!("invalid score");
}
}
}
fn calculate_score() -> i32 {
100
}
fn main() {
match calculate_score() {
0 ..= 59 => {
println!("bad");
},
score if score >= 60 && score <= 100 => {
println!("good, my score is: {}", score);
},
_ => {
println!("invalid score");
}
}
}
运行输出:
good, my score is: 100