首页 Rust 2022 年稳定的语法
文章
取消

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_SUCCESSErr 转换为 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 中发挥很大的作用。

本文由作者按照 CC BY 4.0 进行授权

C++ Concept 重载决议探讨

记录在 Qt 重写事件时犯的蠢