Rust 中级教程 第24课——下划线(Underscore)
0x00 开篇
在 Rust 中 _
是非常常见的标识符。但是在不同的场景下,其意义不同。本篇文章将总结下 _
出现的所有场景。本篇文章阅读时间大约 5 分钟。
0x01 占位符
_
仅仅是一个 通配符,可以匹配任意内容,这也是最常见的一种使用方法。
在 match 中的占位符
如果你了解 C
或者 Java
等语言,那么可以把它看做是 switch
语句中的 default
。来看下示例代码:
fn main() {
let a = 6;
match a % 2 {
1 => {
println!("a % 2 == 1");
}
_ => {
println!("a % 2 == 0");
}
}
}
// 运行结果
// a % 2 == 0
在代码中 a % 2 = 0
,但是在匹配分支中并不存在 0
分支,所以程序会默认来到 _
分支中继续运行。
解构赋值
_
还可以表示解构赋值中的占位符且只能出现在 =
的左侧,如元组等等。示例代码如下:
fn main() {
let (m, _, n) = (4, 5, 6);
println!("m = {}, n = {}", m, n);
}
// 运行结果
// m = 4, n = 6
_
来解构赋值时,可以忽略我们不想处理的值。
0x02 let _
let _
通常可以忽略一个表达式的结果,如果某个表达式的结果值你并不想去处理它,那么可以使用 let _
来忽略。示例代码如下:
fn main() {
let name = String::from("rust");
// 下面一行代码直接忽略掉 replace 之后的值
let _ = name.replace("s", "x");
println!("name = {} ", name);
}
// 运行结果
// name = rust
另外它还可以忽略 #[must_use]
的警告。比如 Result
的返回结果是必须需要处理的,但是可以使用 let _
忽略。
fn main() {
// 忽略 #[must_use]警告
let mut input = String::new();
let _ = std::io::stdin().read_line(&mut input);
}
虽然使用 _
可以使代码更具可读性,并消除编译器的警告,但是它并不会对编译器产生任何优化。
0x03 _ 与所有权
_
与其它变量标识符不同, _
不会复制、移动或借用它匹配的值。
在 let 绑定后,匹配为 _
的值不会发生所有权转移
示例代码如下:
fn main() {
let rust = String::from("hello");
let _ = rust; // 不发生所有权转移,不会被drop
// rust; // 所有权转移,会被drop
println!("rust = {}", rust);
}
// 运行结果
// rust = hello
这里需要注意与单独执行 rust;
的区别。let _ = rust();
的执行效果等同于 rust();
。唯一区别就是后者会立即 drop
掉返回值。
在 match
、 if let
、 while let
和 for
块中,匹配为 _
的值不会发生所有权转移
示例代码如下:
fn main() {
let q = (String::from("x"), String::from("y"), String::from("z"));
match q {
(x, y, _) => {
// q.0 和 q.1 的所有权转移
println!("m = {}, n = {}", x, y);
} // x 和 y 被 drop掉
}
// println!("q = {:?}", q); q中存在被move的值,无法打印
// println!("q.0 = {:?}", q.0); q.0 被move,无法打印
// println!("q.1 = {:?}", q.1); q.0 被move,无法打印
// 打印q.2
println!("q.2 = {:?}", q.2);
} // z 被drop掉
// 运行结果
// q.2 = "z"
在函数、方法、闭包中,匹配为 _
的值会发生所有权转移
当我们在创建函数、方法、闭包时,使用 _
通配符去定义变量,所有权仍会转移至函数、方法、闭包内部。示例代码如下:
fn main() {
// 函数
let u = String::from("hello");
let v = String::from("rust");
function(u, v);
// 下面一行代码报错
// println!("v = {}", v);
// 闭包
let u = String::from("hello");
let v = String::from("rust");
let closure = |a, _| a;
closure(u, v);
// 下面一行代码报错
// println!("v = {}", v);
}
fn function(u: String, _: String) {
println!("u = {}", u);
}
// 运行结果
// u = hello
0x04 省略类型声明
_
可用于省略类型声明。如我们仅指定类型的一部分,让 Rust 推断其余的部分。示例代码如下:
fn main() {
let vec: Vec<_> = vec![1, 2, 3];
println!("{:?}", vec);
}
// 运行结果
// [1, 2, 3]
0x05 生命期省略(Lifetime Elision)
可以在必须指定显式生命期的某些情况下使用 _
省略生命期。有时候省略生命期,可以使代码更加简洁。示例代码如下:
struct Dog<'a> {
name: &'a str,
}
impl Dog<'_> {
fn new(name: &str) -> Dog {
return Dog { name };
}
fn get_name(&self) -> &str {
return self.name;
}
}
// 不使用匿名生命期
// impl<'a> Dog<'a> {
// fn new(name: &str) -> Dog {
// return Dog { name };
// }
//
// fn get_name(&self) -> &str {
// return self.name;
// }
// }
0x06 未命名常量 (Unnamed Constant)
我们可以声明匿名的常量,被声明的常量也无法使用,所以通常我们一般不会使用它。示例代码如下:
// 没有意义
const _: i32 = 1;
0x07 小结
本文总结了在 Rust 中 _
的所有使用场景吧。一定要注意,let _
不是变量绑定,因为 _
不能用作变量。如果你有 C#
的基础,那么其实这里有点儿类似 C#
中的 弃元
。
转载自:https://juejin.cn/post/7226993529963839547