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