Rust 基本语法特性
Rust 基本语法特性
本文梳理了 Rust 中一些常用但容易忽略的语法特性,涵盖格式化宏、编译期函数执行、闭包捕获语义、复合数据类型、泛型分发机制、引用借用规则以及常用字符串方法。
一、println! 格式化宏
println! 宏支持丰富的格式化占位符,下表列出了常用的格式化形式:
| 占位符 | 含义 | 示例 | 输出说明 |
|---|---|---|---|
{} / nothing |
Display trait | println!("{}", 2) |
常规输出 |
{?} |
Debug trait | println!("{:?}", 2) |
调试格式 |
{o} |
八进制 | println!("{:o}", 2) |
→ 2 |
{x} |
十六进制(小写) | println!("{:x}", 255) |
→ ff |
{X} |
十六进制(大写) | println!("{:X}", 255) |
→ FF |
{p} |
指针地址 | println!("{:p}", &val) |
内存地址 |
{b} |
二进制 | println!("{:b}", 2) |
→ 10 |
{e} |
指数(小写) | println!("{:e}", 10000.0) |
→ 1e4 |
{E} |
指数(大写) | println!("{:E}", 10000.0) |
→ 1E4 |
二、const_fn:编译期函数执行
CTFE(Compile-Time Function Execution,编译时函数执行)机制允许在编译阶段完成函数计算,将运行时开销提前消除。
- Rust 2018 版本起已无需添加
#![feature(const_fn)] - 使用
const fn定义的函数必须能在编译期确定返回值,不能有歧义 - 与普通
fn的区别:const fn强制编译器在编译期执行;而const一般用于定义全局常量
1 |
|
三、Never 类型与闭包
3.1 Never 类型
never 类型表示永远不会返回值的计算类型(例如线程退出、panic! 后的分支)。当前属于实验特性,需要在 Nightly 版本下通过 #![feature(never_type)] 显式使用。never 类型可以强制转换为其他任何类型。
3.2 闭包(Closure)
Rust 的闭包是一种匿名函数,具有三个核心能力:
- 像函数一样被调用
- 捕获上下文环境中的自由变量
- 自动推断输入和返回类型
闭包本质上由匿名结构体 + trait 组合实现,同样可以作为返回值使用。
⚠️ 注意:闭包默认按引用捕获变量。如果将闭包作为返回值,函数内局部变量的引用会跟着返回——函数调用结束后局部变量销毁,引用变成悬垂指针(dangling pointer)。解决方式是使用
move关键字将所捕获变量的所有权转移给闭包,使闭包可以安全返回。
1 | fn main(){ |
3.3 闭包的 Trait 自动实现规则
编译器会根据闭包对环境变量的捕获方式,自动为其实现对应的 trait(Fn、FnMut 或 FnOnce):
| 捕获情况 | 是否需要修改 | 是否用 move | 自动实现的 trait |
|---|---|---|---|
| 未捕获任何环境变量 | — | — | Fn |
| 捕获了复制语义类型 | ❌ 不需要 | 有/无 move | Fn |
| 捕获了复制语义类型 | ✅ 需要 | — | FnMut |
| 捕获了移动语义类型 | ❌ 不需要 | ❌ 无 move | FnOnce |
| 捕获了移动语义类型 | ❌ 不需要 | ✅ 有 move | FnOnce |
| 捕获了移动语义类型 | ✅ 需要 | — | FnMut |
对于使用了
move关键字的FnMut闭包:如果捕获的是复制语义类型的变量,则该闭包会自动实现Copy/Clone;否则不会实现。
四、复合数据类型
Rust 提供 4 种复合数据类型:
类型总览
| 类型 | 说明 | 子分类 |
|---|---|---|
| 元组(Tuple) | 固定长度,元素可不同类型 | — |
| 结构体(Struct) | 具名字段集合 | 具名结构体、元组结构体、单元结构体 |
| 枚举体(Enum) | 同一类型多种变体 | — |
| 联合体(Union) | 共享内存,同一段数据多种解释 | — |
4.1 元组
元组中元素可以是不同类型,长度固定,通过索引访问:
1 | let tuple: (&'static str, i32, char) = ("hello", 5, 'c'); |
4.2 结构体
结构体遵循驼峰命名法,字段默认不可变,字段类型可以是任意类型(包括自身):
1 |
|
4.3 单元结构体
单元结构体不携带任何数据。在 Release 编译模式下,多个单元结构体实例可能被优化为同一对象;Debug 模式下则不会进行此优化:
1 | struct Empty; |
4.4 集合容器
标准库提供了丰富的集合容器类型:
| 分类 | 容器 | 说明 |
|---|---|---|
| 线性序列 | Vec、VecDeque、LinkedList | 向量、双端队列、链表 |
| Key-Value 映射 | HashMap、BTreeMap | 无序哈希表、有序红黑树映射 |
| 集合 | HashSet、BTreeSet | 无序集合、有序集合 |
| 优先队列 | BinaryHeap | 二叉堆 |
双端队列
VecDeque同时实现了push_front(栈行为)和push_back(队列行为)。实际开发中优先使用Vec或VecDeque,它们比链表更快速、内存访问效率更高,且能更好地利用 CPU 缓存。
五、泛型与 Trait
5.1 Option 匹配
1 | fn match_option<T: Debug>(op: Option<T>) { |
5.2 静态分发 vs 动态分发
这是 Rust 泛型的两种核心调度方式:
| 特性 | 静态分发(Static Dispatch) | 动态分发(Dynamic Dispatch) |
|---|---|---|
| 实现方式 | 泛型 <T: Trait> |
trait 对象 &dyn Trait |
| 调度时机 | 编译期展开具体类型代码 | 运行时通过 vtable 查找 |
| 运行开销 | 零开销抽象 | 少量虚函数调用开销 |
| 二进制体积 | 每种类型生成一份副本 | 仅生成一份 |
静态分发示例:fly_static::<Pig>(pig) 和 fly_static::<Duck>(duck) 在编译期各自生成特化代码,抽象在编译阶段就被消除了。
动态分发示例:fly_dyn(&Duck) 通过 trait 对象在运行时查找方法实现,带来少量开销(通常可忽略不计)。
1 | trait Fly { |
5.3 重要标签 Trait(Marker Traits)
Rust 在 std::marker 模块中定义了 5 个重要的标签 trait:
| Trait | 作用 |
|---|---|
| Sized | 标识编译期可确定大小的类型。编译器依赖它来识别哪些类型可以在栈上分配 |
| Unsize | (实验性)标识动态大小类型 DST,如 [T] 或 dyn Trait |
| Copy | 标识可以按位安全复制的类型(所有字段均需实现 Copy) |
| Send | 标识可以跨线程安全传递所有权的类型 |
| Sync | 标识可以在线程间安全共享引用的类型(&T 是 Send 的当且仅当 T: Sync) |
Sized是最核心的标签 trait——几乎所有泛型约束都隐含T: Sized。若要支持动态大小类型,需显式使用T: ?Sized。
#[fundamental]属性表示该 trait 不受孤儿规则的限制。通过#[derive(Debug, Copy, Clone)]可以让结构体自动实现 Copy,但前提是所有成员都是可复制的——否则按位复制可能引发内存安全问题。
六、引用与借用
6.1 基本概念
引用(Reference)是 Rust 提供的一种指针语义。它与裸指针的区别在于:指针保存的是内存地址,而引用是某块内存的别名(Alias),必须满足编译器的安全检查规则。
- 不可变引用:
&T—— 只读借用 - 可变引用:
&mut T—— 可写借用
在所有权体系中,引用也称为借用(Borrowing)。借用不会转移所有权,但会对原所有者施加限制。
6.2 借用三大规则
为保证内存安全,Rust 的借用检查器强制执行以下三条规则:
| 规则 | 内容 | 防止的问题 |
|---|---|---|
| 规则一 | 借用的生命周期不得长于出借方(owner)的生命周期 | 悬垂指针(dangling pointer) |
| 规则二 | 可变引用不允许有别名(独占性) | 数据竞争(data race) |
| 规则三 | 不可变借用在存在期间不能再出借为可变借用 | 违反不变量保证 |
规则二和三的核心原则概括为八个字:共享不可变,可变不共享。
从读写锁的角度理解:同一时刻,要么持有一个写锁(可变引用),要么持有多个读锁(不可变引用),两者互斥。
6.3 内部可变性(Interior Mutability)
当需要在拥有不可变引用的前提下修改数据时,Rust 提供了以下几种内部可变性容器:
| 类型 | 适用场景 | 机制 |
|---|---|---|
| Cell<T> | T: Copy 类型 |
提供 get() / set() 方法 |
| RefCell<T> | 非 Copy 类型 | 提供 borrow() / borrow_mut() 方法,运行时检查借用规则,违规时触发 panic |
| Cow<T> (Clone on Write) | 写时复制 | Borrowed 包裹引用,Owned 包裹所有权 |
Cow 的关键方法:
to_mut():获取可变借用,首次调用时克隆,后续调用复用第一次的克隆结果。如果T本身就是Owned,则不会发生额外克隆into_owned():获取一个拥有所有权的对象。如果是Borrowed则克隆转为Owned;如果是Owned则直接转移所有权
七、字符串常用方法
char 类型提供了丰富的判断和转换方法,均基于 Unicode 字符集:
| 方法 | 功能说明 | 示例 |
|---|---|---|
is_digit(radix) |
判断字符是否为指定进制的数字字符 | '5'.is_digit(10) → true |
to_digit(radix) |
将字符转换为目标进制的数值 | 'f'.to_digit(16) → Some(15) |
is_lowercase |
判断是否为小写字母(Unicode Lowercase) | 'a'.is_lowercase → true |
is_uppercase |
判断是否为大写字母(Unicode Uppercase) | 'A'.is_uppercase → true |
to_lowercase |
转为小写(完整 Unicode 映射) | 'A'.to_lowercase() → "a" |
to_uppercase |
转为大写(完整 Unicode 映射) | 'a'.to_uppercase() → "A" |
is_whitespace |
判断是否为空白字符(空格、制表符等) | ' '.is_whitespace → true |
is_alphabetic |
判断是否为字母(汉字也算字母) | '字'.is_alphabetic → true |
is_alphanumeric |
判断是否为字母或数字 | 'a'.is_alphanumeric → true |
is_control |
判断是否为控制字符 | '\n'.is_control → true |
is_numeric |
判断是否为数字(含 Unicode 数值字符) | '٣'.is_numeric → true(阿拉伯数字) |
escape_default |
转义 \t、\r、\n、引号等特殊符号 |
返回转义后的字符串迭代器 |
以上方法均定义在
char原始类型上,返回值为bool或Option<T>等。其中涉及大小写转换的方法会处理完整的 Unicode 映射关系(如德语ß→SS),而非简单的 ASCII 替换。
以上就是 Rust 中常见的基础语法特性。掌握了这些,日常编码的大部分场景就都能应对了。如果想进一步深入,建议继续探索生命周期标注、模式匹配的高级用法、异步编程(async/await)以及宏编程等主题。