likes
comments
collection
share

Rust 中级教程 第24课——下划线(Underscore)

作者站长头像
站长
· 阅读数 88

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.0q.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
评论
请登录