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);