Rust 语法与关键字字典。体例对齐站内 Go 关键字篇:每条给语义、多场景示例、边界与坑。Rust 关键字数量少,但语法机制(所有权/生命周期/trait)才是真正的学习成本,故合并为条目。
1. let / let mut / const / static – 绑定
描述
let 绑定默认不可变,mut 开可变;const 是编译期常量(必须标类型、可内联);static 是程序级全局(有固定内存地址)。遮蔽(shadowing)允许同名重绑并换类型。
语法和示例
let x = 5; // 不可变
let mut y = 0; y += 1; // 可变
const MAX: u32 = 100; // 编译期常量,必须标类型
static NAME: &str = "app"; // 全局静态
static mut COUNTER: u32 = 0; // 可变静态需 unsafe 访问
let s = "42";
let s = s.parse::<i32>().unwrap(); // 遮蔽可换类型
let (a, b) = (1, 2); // 元组解构注意事项
- const 必须是编译期可求值的表达式,不能调运行时函数
- static mut 访问需 unsafe,且无编译期并发保护——生产用 OnceLock/AtomicU32
- 遮蔽不是重新赋值:新绑定占用新内存,旧值在作用域结束后才释放
- mut 只对当前绑定生效;let s2 = &mut s 与 let mut s 是两回事
2. fn – 函数
描述
snake_case 命名;参数必须标类型;返回值用 -> 声明,尾表达式即返回(不用 return 关键字)。函数可嵌套定义;main 是入口。
语法和示例
fn add(a: i32, b: i32) -> i32 { a + b } // 尾表达式
fn early(n: i32) -> i32 {
if n < 0 { return 0; } // 提前返回可用 return
n * 2
}
fn no_return() {} // 隐式返回 ()
fn diverging() -> ! { loop {} } // 永不返回
fn generic<T: Clone>(x: T) -> T { x.clone() }
fn take_ownership(s: String) -> String { s } // 所有权移动
fn borrow(s: &str) -> usize { s.len() } // 借用
// 函数作为值
let f: fn(i32) -> i32 = add;注意事项
- 尾表达式后面不能加分号——加了分号就变成语句,返回 ()
- 参数类型必须显式标注(不像 let 可推断)
- 返回引用的函数通常需要生命周期标注;返回 Box 可规避
- 函数名冲突时用模块路径区分:std::cmp::min vs my::min
3. struct / impl – 结构体与方法
描述
具名结构体定义数据;impl 块添加方法(第一个参数是 &self/&mut self)或关联函数(无 self,用 Type::fn 调用)。一个类型可有多个 impl 块。
语法和示例
struct User { name: String, age: u8 }
impl User {
fn new(name: impl Into<String>) -> Self { // 关联函数
Self { name: name.into(), age: 0 }
}
fn birthday(&mut self) { self.age += 1; } // &mut self
fn name(&self) -> &str { &self.name } // &self
fn consume(self) -> String { self.name } // 消耗 self
}
let mut u = User::new("Li");
u.birthday();
// 元组结构体 / 单元结构体
struct Meters(f64);
struct Marker;
// 结构体更新语法
let u2 = User { age: 30, ..u }; // 其余字段从 u 取注意事项
- Self 是当前类型的别名,写在 impl 内比写 User 更利于泛型
- 方法名与字段名可同名(u.name() 与 u.name 不冲突)
- impl 块可以拆成多个(按功能分文件),效果等价
- ..u 会移动剩余字段(非 Copy 类型时 u 后续不可用)
- 结构体字段默认私有;跨模块访问需 pub
4. enum / match – 代数数据类型
描述
enum 每个变体可带不同类型数据;match 必须穷尽所有变体(或用 _ 兜底),且是表达式可返回值。支持守卫、范围、解构、@ 绑定。
语法和示例
enum Msg {
Quit,
Move { x: i32, y: i32 },
Write(String),
}
let n = match msg {
Msg::Quit => 0,
Msg::Move { x, y } => x + y,
Msg::Write(s) if s.len() > 5 => 1, // 守卫
Msg::Write(_) => 2,
};
// 范围与多重匹配
match n {
1 | 2 => "小",
3..=5 => "中", // 闭区间
x @ 6..=10 => format!("大 {x}"), // @ 绑定
_ => "超大",
}
// if let / let-else
if let Msg::Write(s) = msg { use(s); }
let Some(v) = opt else { return; };注意事项
- match 必须穷尽——编译器会列出未覆盖的变体(这是它优于 switch 的核心)
- match 的 _ 兜底应放最后,且会掩盖「新增变体」的编译错误提示
- match 是表达式,各分支返回类型必须一致
- if let 只处理一个变体;多个变体用 let-else 或 match
- enum 变体默认不实现 Copy/Debug,需 #[derive]
5. Option – 可选值
描述
标准库枚举 Some(T)/None,替代 null。可与 ? 组合传播;有丰富的组合子(map/and_then/filter/or_else)。
语法和示例
let v: Option<i32> = Some(5);
v.unwrap(); // None 时 panic
v.unwrap_or(0); // 默认值
v.unwrap_or_else(|| compute()); // 惰性默认
v.map(|x| x * 2); // 有值才转换
v.filter(|x| *x > 3);
v.ok_or("缺值")?; // 转 Result 传播
v.and_then(|x| Some(x + 1));
// 模式匹配
match v {
Some(x) => println!("{x}"),
None => println!("无"),
}注意事项
- unwrap/expect 只用于原型或「逻辑上不可能为 None」处,生产用 unwrap_or / ?
- Option> 是嵌套,flatten 可压平——但通常说明设计有问题
- Option 大小通常与 T 相同(None 用零值标记),Box/Option 组合才多开销
- 不要用 Option 表达「空集合」,空 Vec 本身已表达没有元素
6. Result 与 ? – 错误处理
描述
Ok(T)/Err(E)。? 运算符遇 Err 立即 return,并通过 From trait 自动转换错误类型。main 也可返回 Result。
语法和示例
use std::{fs, io};
fn read(p: &str) -> Result<String, io::Error> {
let s = fs::read_to_string(p)?; // ? 自动 return Err
Ok(s)
}
// 错误类型可自动转换(实现 From 时)
fn parse_num(p: &str) -> Result<i32, Box<dyn std::error::Error>> {
let s = fs::read_to_string(p)?; // io::Error → Box<dyn Error>
let n = s.trim().parse::<i32>()?; // ParseIntError → Box<dyn Error>
Ok(n)
}
// 组合子
read("a")
.map(|s| s.len())
.map_err(|e| format!("读取失败: {e}"));
// main 返回 Result
fn main() -> Result<(), Box<dyn std::error::Error>> {
let s = read("a")?;
Ok(())
}注意事项
- ? 只能用在返回 Result/Option 的函数里,否则编译错
- ? 会做 From 转换——自定义错误类型需实现 From 或用 map_err
- 库用 thiserror 定义具体错误;应用用 anyhow 简化传播(常见组合)
- 不要用 unwrap 掩盖错误;panic! 只用于不可恢复的不变量违反
- ? 在 Option 上下文里会把 Err 丢弃变 None——注意语义丢失
7. trait – 特征与能力
描述
共享行为的契约。可有默认实现、关联类型、关联常量、泛型方法。实现后可静态分发(泛型)或动态分发(dyn)。
语法和示例
trait Speak {
const VOLUME: u8 = 5; // 关联常量
type Output; // 关联类型
fn say(&self) -> String;
fn loud(&self) -> String { // 默认实现
format!("{} {}", self.say(), Self::VOLUME)
}
}
impl Speak for Dog {
type Output = String;
fn say(&self) -> String { "woof".into() }
}
// derive 派生常用 trait
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
struct P { x: i32 }
// 手写 Display
impl std::fmt::Display for P {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "P({})", self.x)
}
}
// trait 对象
let speakers: Vec<Box<dyn Speak>> = vec![Box::new(Dog)];注意事项
- trait 不能有字段(要状态得在实现类型里)
- dyn Trait 需要 Sized 兼容——返回 dyn 通常包 Box
- trait 方法接收 &self 才能被 trait 对象调用;self 会要求 Sized
- 孤儿规则:只能为「本 crate 的类型」或「本地 trait」实现——两者至少一个本地
- async fn in trait 1.75+ 稳定,但生成 !Send 的 future,跨线程需额外处理
8. 泛型与 impl Trait – 静态与动态分发
描述
泛型单态化生成专用代码(零开销);dyn Trait 走 vtable(运行时分发);impl Trait 是泛型的语法糖。返回位置 impl Trait 可隐藏具体类型。
语法和示例
fn max_of<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut m = list[0];
for &x in list { if x > m { m = x; } }
m
}
fn show(x: impl std::fmt::Display) { println!("{x}"); }
fn make_iter() -> impl Iterator<Item = i32> { (0..3).map(|x| x * 2) }
// where 子句(约束多时可读性更好)
fn complex<T, U>(a: T, b: U) -> String
where T: std::fmt::Debug, U: std::fmt::Display {
format!("{a:?} {b}")
}
// dyn
let handlers: Vec<Box<dyn Fn(i32) -> i32>> = vec![Box::new(|x| x + 1)];注意事项
- 泛型单态化会增大二进制体积;大量不同实例时考虑 dyn
- impl Trait 在参数位置 = 泛型;在返回位置 = 隐藏具体类型(只能有一个 impl Trait 返回)
- dyn Trait 有虚调用开销(通常可忽略),且不能用于 Sized 要求处
- 泛型约束越少编译越快;把约束放 where 子句便于阅读
- turbofish 语法 :: 在表达式里显式指定类型:parse::()
9. 生命周期 ‘a – 引用存活关系
描述
标注引用之间的存活关系,不改变实际生命周期。三条省略规则覆盖多数场景:①每个输入引用各自一个生命周期 ②只有一个输入引用时输出继承它 ③&self/&mut self 的方法输出继承 self。
语法和示例
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() > b.len() { a } else { b }
}
// 结构体持引用必须标
struct Parser<'a> { src: &'a str }
impl<'a> Parser<'a> {
fn new(s: &'a str) -> Self { Self { src: s } }
fn first(&self) -> &str { &self.src[..1] } // 省略规则③生效
}
// 'static:整个程序存活(字符串字面量、泄漏的内存)
fn store(s: &'static str) {}
static NAME: &'static str = "app";
// 多生命周期
fn pick<'a, 'b>(x: &'a str, y: &'b str) -> &'a str { x }注意事项
- 生命周期标注不延长任何值的实际存活时间,只是向编译器描述关系
- 返回引用必须能追溯到一个输入引用,否则编译错(要拥有就返回所有权)
- 泛型 T: ‘static 表示「T 不含短命引用」,不等于「T 必须活得很久」
- struct 持引用几乎都需要生命周期参数——若嫌麻烦考虑用拥有的类型(String 替代 &str)
- 闭包捕获引用时生命周期由编译器推断,跨线程需 ‘static 或 scoped thread
10. 闭包 – |args| body
描述
匿名函数,可捕获环境。按捕获方式自动实现 Fn(只读)/FnMut(可变)/FnOnce(消耗)。move 强制夺取所有权。
语法和示例
let add = |a: i32, b: i32| a + b;
let g = String::from("hi");
let take = move || println!("{g}"); // g 移入闭包
let mut count = 0;
let mut incr = || count += 1; // FnMut
incr(); incr();
// 作为参数(三种 trait 从宽到严)
fn call_twice<F: Fn()>(f: F) { f(); f(); }
fn call_mut<F: FnMut()>(mut f: F) { f(); }
// 返回闭包需 impl Fn 或 Box<dyn Fn>
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
move |x| x + n
}注意事项
- 闭包捕获默认按最小权限(能借用就不移动);move 强制全部转移
- 返回闭包时必须 move,否则借用局部变量无法逃逸
- FnOnce 只能调一次;需要多次调用但会消耗捕获值的闭包编译不过
- 闭包类型是匿名的,无法写出——用 impl Fn 或 Box
- 多个闭包捕获同一可变引用时编译错(借用规则同样适用)
11. 所有权与借用 – 核心规则
描述
①每个值有唯一所有者 ②同一时刻只能有一个所有者(赋值/传参即移动)③所有者离开作用域时 Drop。借用 &T 可多个只读,&mut T 独占。
语法和示例
let s1 = String::from("hi");
let s2 = s1; // 移动:s1 失效
// println!("{s1}"); // borrow of moved value
let n = 5;
let m = n; // i32 是 Copy:都可用
// 借用规则
let mut v = vec![1];
let r = &v; // 不可变借用
println!("{r:?}"); // r 最后使用处
v.push(2); // r 已死,可变借用 OK
let w = &mut v;
w.push(3);
// 克隆 vs 借用
let owned = s2.clone(); // 显式深拷贝(代价)
fn takes(s: &str) {} // 借用:调用方保留所有权注意事项
- Copy 类型(整数/浮点/bool/char/只含它们的元组/数组)赋值不移动
- 非 Copy 类型(String/Vec/自定义 struct)赋值即移动;要保留用 clone 或传引用
- NLL(非词法生命周期):借用到最后一次使用为止,不必等到作用域结束
- 函数返回引用时,所有权仍在原处——调用方必须保证源活得够久
- 递归数据结构必须用 Box/Rc,否则大小无限
12. unsafe – 解除编译器保护
描述
unsafe 块内允许五件事:解裸指针、调 unsafe 函数、访问修改 static mut、实现 unsafe trait、访问 union 字段。unsafe 不是「关掉检查」,而是把不变量责任转移给程序员。
语法和示例
// 裸指针
let x = 5;
let raw = &x as *const i32;
unsafe { println!("{}", *raw); }
// unsafe 函数
unsafe fn legacy_api() {}
unsafe { legacy_api() }
// 安全封装(标准库的做法)
pub struct SafeWrapper(*mut u8);
impl SafeWrapper {
pub fn new() -> Self { Self(std::ptr::null_mut()) }
pub fn write(&mut self, v: u8) {
unsafe { *self.0 = v; } // unsafe 藏在内部
}
}
// FFI
extern "C" fn callback() {}
#[no_mangle] pub extern "C" fn exported() {}注意事项
- unsafe 块有不少于安全代码的责任——编译器不再保证内存安全
- 写 unsafe 的纪律:包一层安全 API,把 unsafe 关键字挡在内部并在注释里写明不变量
- 调试:Miri(cargo +nightly miri run)能检测未定义行为
- static mut 与多线程组合极危险,优先用 AtomicXxx 或 Mutex
- unsafe trait 的实现意味着「调用方依赖这个不变量」
13. 宏 – macro_rules! 与派生
描述
macro_rules! 是声明宏(模式匹配展开);#[derive] 等是过程宏(编译期生成代码)。宏可接受可变参数、重复模式。
语法和示例
// 使用内置宏
println!("{}", x); format!("{:.2}", v);
vec![1, 2, 3]; assert_eq!(a, b);
dbg!(&v); todo!(); unimplemented!();
matches!(x, Some(_));
write!(f, "text")?;
// 自定义声明宏
macro_rules! my_vec {
($($x:expr),*) => {
{ let mut v = Vec::new(); $(v.push($x);)* v }
};
}
let v = my_vec![1, 2, 3];注意事项
- 宏与函数的区别:宏在编译期展开,可接受变长参数与不同类型
- 宏里的变量作用域与调用处不同(卫生性),避免命名冲突
- cargo expand 可查看宏展开结果(调试必备)
- 过程宏需单独的 proc-macro crate,且只能在编译期用
- 不要滥用宏——能用泛型/函数解决时优先它们(可读性更好)