Rust 2022 年稳定的语法
概览
在整个 2022 年,Rust 一共发布了 1.58.0 ~ 1.66.0 共 9 个版本,让我们感谢 Rust 团队一整年的付出。
通常来说,大部分人都不是喜欢追着 Release Note 啃的类型,因此对于大部分人而言,Rust 的语法就只有书上写出来的那一些。这也是我撰写这篇文章的目的:总结和记录 Rust 整个 2022 年稳定的语法,让更多人意识到 “原来 Rust 还支持这种写法!”。
那么,让我们开始吧。
字符串格式化可以捕捉标识符
- 稳定于:1.58.0
从这个版本开始,Rust 可以在格式化字符串时捕捉上下文中的变量:
1
2
3
4
5
fn main() {
let s1 = "Hello";
let s2 = "world";
println!("{s1} {s2}");
}
不仅如此,上下文变量还可以用作格式化参数(注:由于 HTML 的特性,width 参数引入的空格会被消除,因此点击下方按钮得到的运行结果与实际效果有差别):
1
2
3
4
5
6
fn main() {
let value = 114.514;
let width = 10;
let precision = 1;
println!("{value:width$.precision$}");
}
内联汇编
- 稳定于:1.59.0
用官方的例子来展示,下面的代码会将 x 乘以 6:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use std::arch::asm;
fn main() {
let mut x: u64 = 4;
unsafe {
asm!(
"mov {tmp}, {x}",
"shl {tmp}, 1",
"shl {x}, 2",
"add {x}, {tmp}",
x = inout(reg) x,
tmp = out(reg) _,
);
}
println!("x is {x}");
}
解构赋值
- 稳定于:1.59.0
从这个版本开始,可以在等号左侧使用元组、切片和结构模式进行解构赋值。值得注意的是,let
绑定一直以来都支持解构,这个语法使得赋值和绑定在语法上更加统一:
1
2
3
4
5
6
7
8
9
10
11
12
struct S {
e: i32,
f: i32,
}
fn main() {
let (a, b, c, d, e);
(a, b) = (1, 2);
[c, .., d, _] = [1, 2, 3, 4, 5];
S { e, .. } = S { e: 5, f: 3 };
println!("{:?}", (a, b, c, d, e));
}
泛型常量优化
- 稳定于:1.59.0
在此之前,Rust 要求泛型常量参数必须位于泛型参数列表的最后,而在此版本之后不再对此做出要求;另一方面,从这个版本开始,泛型常量参数可以有默认值(只能用于定义 struct, enum, type 和 trait),但是拥有默认值的泛型常量参数仍然需要放在泛型参数列表的最后:
1
2
3
4
5
6
7
8
9
10
11
12
13
#[derive(Debug)]
struct TwoArr<T, const M: usize, U, const N: usize = 10> {
arr1: [T; M],
arr2: [U; N],
}
fn main() {
let two_arr = TwoArr {
arr1: [1, 2, 3, 4],
arr2: ["hello", "world"],
};
println!("{two_arr:?}");
}
main
函数自定义退出代码
- 稳定于:1.61.0
最初,main
函数只能返回 ()
,如果想要异常退出,只能通过 process::exit(code)
。
自 Rust 1.26.0 以来,main
函数允许返回 Result
,其中 Ok
转换为 EXIT_SUCCESS
而 Err
转换为 EXIT_FAILURE
。
在 Rust 1.61.0 中,Termination
特性迎来了稳定,允许我们为自定义枚举实现 Termination
并作为 main
函数的返回值类型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use std::process::{ExitCode, Termination};
#[repr(u8)]
pub enum GitBisectResult {
Good = 0,
Bad = 1,
Skip = 125,
Abort = 255,
}
impl Termination for GitBisectResult {
fn report(self) -> ExitCode {
// Maybe print a message here
ExitCode::from(self as u8)
}
}
fn main() -> GitBisectResult {
// Do other things that may return a Error Code
GitBisectResult::Good
}
默认枚举变体
- 稳定于:1.62.0
现在,我们可以为枚举的某一变体(Variant)指定 #[default]
来辅助实现 #[derive(Default)]
,仅有不包含字段的变体才可以用于指定 #[default]
:
1
2
3
4
5
6
7
8
9
10
11
12
#[derive(Default, Debug)]
enum MyOption<T> {
#[default]
MyNone,
MySome(T),
}
fn main() {
let my_none = MyOption::<i32>::default();
println!("{my_none:?}");
}
泛型关联类型
- 稳定于:1.65.0
简称 GAT,是 Rust 社区长久以来一直在呼吁的一个特性。这个语法描述起来很简单,它允许你在定义关联类型时使用泛型参数(类型、声明周期、常量):
1
2
3
trait Foo {
type Bar<'x>;
}
正如 Rust 团队所说,很难用简短的语言来描述这个语法究竟有多大的用处,The push for GATs stabilization 这篇文章能让你对此有更进一步的理解。
let
-else
语句
- 稳定于:1.65.0
这个版本引入了一种新的 let
语句,它允许使用 else
块来处理模式不匹配的情况。else
块必须发散(Diverge),这意味着 break
, return
或者 panic!
这类语句:
1
2
3
4
fn main() {
let input = Option::<i32>::None;
let Some(_val) = input else { panic!("Expected a value") };
}
跳出任意标记块
- 稳定于:1.65.0
从该版本开始,break
可以跳出任意一个代码块,这听起来有点像 goto
,但实际上,break
只是从代码块的内部跳到了代码块的结尾,而不是 goto
那种任意跳转:
1
2
3
4
5
6
7
8
9
10
11
12
13
fn main() {
let sum = 'mylabel: {
let condition = false;
// do something here that may change condition to true
if condition {
break 'mylabel 1;
}
// do something here
2
};
println!("{sum}");
}
带字段的枚举的显式判别式
- 稳定于:1.66.0
在该版本之前,我们只能在所有变体都没有字段的枚举上使用显式判别式:
1
2
3
4
5
6
7
#[repr(u8)]
enum Bar {
A,
B,
C = 42,
D,
}
现在,即使带有字段,我们仍然可以使用显式判别式:
1
2
3
4
5
6
#[repr(u8)]
enum Foo {
A(u8),
B(i8),
C(bool) = 42,
}
此特性将在跨语言 FFI 中发挥很大的作用。