Apr 05, 2025
3 min read
Rust,

Rust中那些“不常见但超有用”的语法,你知道几个?

本文介绍了 Rust 中一些不常见但实用的语法特性,包括联合体(`union`)、切片模式匹配、裸指针操作、内联汇编(`asm!`)、extern 块、loop 循环返回值、匹配模式中的 `@`、标签化循环控制、never 类型(`!`)、`#[repr]` 属性及宏的多样化调用方式。这些特性在特定场景下可显著提升开发效率和代码灵活性。

Rust 中有一些语法特性虽然存在,但由于使用场景特殊或相对复杂,较少被开发者日常使用。 当然,常用和不常用的定义是相对的,由于 Rust 的用途比较广泛,可能有一些不常用的语法,在其他一些领域却非常常见是很正常的事。

联合体 (union)

rust 支持类似于 C 的联合体。联合体声明使用与结构声明相同的语法,只是将 struct 替换为 union 。联合体的关键特性是所有字段共享相同的存储空间。因此,对联合体某个字段的写入可能会覆盖其它的字段,联合体的尺寸由其最大字段的尺寸决定。

fn main() {
    #[repr(C)]
    union MyUnion {
        i: i32,
        f: f32,
    }
    let u = MyUnion { i: 42 };
    println!("{}", unsafe { u.i })
}

切片模式匹配

rust 支持通过 .. 通配符匹配切片的头部和尾部。这个平时代码中比较少用(如果你常用肯定会不同意),不过,在深度学习中很常见,比如 candle 中: tensor.i((.., ..4))?

fn main() {
    let arr = [1, 2, 3, 4];
    let &[a, .., b] = &arr;
    println!("First: {}, Last: {}", a, b); // 输出 1 和 4

    let arr = [
        [1,2,3],
        [4,5,6],
        [7,8,9],
    ];
    println!("{:?}", &arr[1][..]); //[4, 5, 6]
    println!("{:?}", &arr[1][1..]); //[5, 6]
}

.. 还有一个少见的用法,就是用来更新字段.

fn main() {
    #[derive(Debug)]
    struct Point {
        x: i32,
        y: i32,
    }
    let p1 = Point { x: 1, y: 2 };
    let p2 = Point { y: 3, ..p1 }; // p2.x = 1, p2.y = 3
    println!("p2 = {:?}", p2);
}

裸指针操作 (*const T, *mut T)

*const T*mut T 可以直接操作内存地址。这个在平时项目中并不常见,原因是大部分人使用 Rust 都是使用 safe 部分。而直接操作内存是一个 unsafe 的操作。不过,对于库作者来说,这个很常见,因为在做 FFI 编程时,是一定会见到的。 直接操作内存地址需要在 unsafe 块中使用。

fn main() {
    let a = 42;
    let ptr = &a as *const i32;
    unsafe {
        println!("{}", *ptr); // 输出 42
    }
}

内联汇编 (asm!)

rust 是可以直接嵌入汇编代码的。

fn main() {
    let x: u64;
    unsafe {
        asm!("mov {}, 5", out(reg) x);
    }
    assert_eq!(x, 5);
}

外部函数接口 extern 块

extern 关键字在老版本的 rust 中应该很常见,不过现在基本用在与 C 语言交互上了。

fn main() {
    extern "C" {
        fn printf(fmt: *const u8, ...);
    }
    unsafe {
        printf("Hello, world!\0".as_ptr());
    }
}

loop 循环返回值

通过 break 表达式可直接从 loop 循环返回值:

fn main() {
    let result = loop {
        break 42; 
    };

    println!("result: {}", result);
}

匹配模式中的 @

@ 在模式匹配时同时绑定变量和匹配值。

	let value = Some(5);
    match value {
        Some(x @ 1..=10) => {
            println!("x: {}", x);
        }, 
        _ => {},
    }

标签化循环控制

    let mut x = 0;
    'outer: loop {
        x +=100;
        loop {
            x += 1;
            if x % 7 == 0 {
                println!("x is {}",x); //105
                break 'outer; 
            }
        }
    }

never 类型: !

never 表示永不返回的函数,注意:这个和不写返回值是不一样的。

fn forever() -> ! {
    loop {}
}

#[repr] 属性

从前面的示例我们也可以看到,[repr] 属性是用来控制类型的内存布局的(如 #[repr(C)] 兼容 C),这仅在 FFI 或底层优化时使用。

#[repr(C)]
struct Point {
    x: i32,
    y: i32,
}

宏的调用

由于代码的智能提示,很多人习惯了 vec![]的写法,实事上,宏的调用还可以是vec!() 或者 vec!{}。 使用中括号只是习惯用法而已。

    let data1 = vec![1, 2, 3, 4, 5];
    let data2 = vec!(1, 2, 3, 4, 5);
    let data3 = vec!{1, 2, 3, 4, 5};

    println!("data1: {:?}", data1);
    println!("data2: {:?}", data2);
    println!("data3: {:?}", data3);