Rust 关键字与语法详解

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,且只能在编译期用
  • 不要滥用宏——能用泛型/函数解决时优先它们(可读性更好)
上一篇
下一篇