Skip to content

Rust

已收录:本页完成领域概览规划清单中 Rust 的 4 项内容(所有权/借用与生命周期、trait 与泛型系统、错误处理、常用 crate 与 async 生态)。示例经本机 rustc 1.98(edition 2024)编译验证,第三方 crate 示例依赖需 cargo add(网络可用时直连 crates.io)。

本页按「语法要点 → 运行机制 → 示例 → 踩坑」组织:

  1. 所有权、借用与生命周期
  2. trait 与泛型系统
  3. 错误处理:Result / Option
  4. 常用 crate 与 async 生态

所有权、借用与生命周期

所有权三规则(语法要点)

  1. Rust 中每个值有且仅有一个所有者(owner);
  2. 同一时刻值只能被一个变量拥有;
  3. 所有者离开作用域时,值被 drop(自动释放)。

与 Go/Java 的 GC、Python/JS 的引用计数不同:Rust 在编译期静态决定释放点,运行时零开销、无 GC 停顿。这也决定了「拷贝 vs 移动」的显式语义。

移动语义与 Copy(运行机制)

赋值、传参、返回默认按移动处理:所有权转移,原变量不可再用。标量等 Copy 类型例外——它们按位拷贝、原变量仍可用。

rust
// 编译运行:rustc 1.98,cargo run
fn main() {
    let s = String::from("hello");
    let t = s;             // String 非 Copy → 所有权移动给 t
    // println!("{s}");    // E0382: borrow of moved value: `s`
    println!("{t}");       // hello

    let x = 42;
    let y = x;             // i32 是 Copy → x 仍可用
    println!("x={x}, y={y}"); // x=42, y=42
}

常用类型是否 Copy

Copy非 Copy(移动)
全部标量(i32/u64/f32/bool/char)String、Vec、HashMap 等堆类型
&T(不可变引用)&mut T(可变引用不可 Copy)
只含 Copy 的元组/数组((i32, i32)含非 Copy 的元组/数组
自定义结构体显式派生才 Copy默认不 Copy,按需 #[derive(Clone)]

需要保留原值且无法 Copy 时显式 clone()(深拷贝,需类型实现 Clone)。

借用规则(语法要点 + 编译期检查)

& 借用而非转移所有权。两条铁律(编译器保证,违反即编译错误):

  • 同一时刻,一个值只能有一个 &mut 可变借用,或多个 & 共享借用,二者不可混用
  • 借用不能超过所有者的生命周期(杜绝悬垂引用)。
rust
// 编译运行:rustc 1.98
fn main() {
    let mut v = vec![1, 2, 3];

    let first = &v[0];     // 共享借用(不可变)
    println!("{first}");   // 借用使用结束(NLL:非词法生命周期)
    v.push(4);             // ✅ first 已不再使用,可变借用才被允许

    let r = &mut v;        // 独占可变借用
    r.push(5);
    // let r2 = &mut v;    // E0499: 不能同时有两个可变借用
    println!("{:?}", v);   // [1, 2, 3, 4, 5]
}

NLL(non-lexical lifetimes)允许「借用提前结束」——上面 first 在最后使用后即可释放,v.push 才合法。

字符串与切片(运行机制)

String 拥有堆上字节;&str 是视图(借用,无所有权)。&String 可经 Deref 自动转为 &str。切片 &[T] 是数组/向量的视图:

rust
// 编译运行:rustc 1.98
fn take_slice(xs: &[i32]) -> i32 {
    xs.iter().sum()
}

fn main() {
    let arr = [1, 2, 3, 4];
    println!("{}", take_slice(&arr[1..3])); // 5

    let v = vec![10, 20, 30];
    println!("{}", take_slice(&v)); // 60(&Vec<T> 自动转 &[T])

    let s = String::from("hello world");
    let word = &s[..5];            // 视图,无拷贝
    println!("{word}");            // hello
}

生命周期标注(语法要点)

生命周期是编译期概念:标注 'a 说明「返回引用与哪个入参活得一样久」,不产生运行时开销。三种常见省略规则:入参各得一个生命周期、只有一个入参则返回引用的生命周期与之相同、方法中 &self 占一个。

rust
// 编译运行:rustc 1.98
// longest 的返回引用绑定到较小的那个入参生命周期
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() >= y.len() { x } else { y }
}

struct Book<'a> {           // 结构体存引用必须标注生命周期
    title: &'a str,
}

fn main() {
    let title = String::from("The Rust Book");
    let book = Book { title: &title }; // title 活得比 book 久 → 合法
    println!("{}: {}", longest("a", "bbb"), book.title);
}

'static 指「整个程序运行期」:字符串字面量就是 &'static str;全局静态、Box::leak 产物亦属此类。注意 'static 是约束而非绝对寿命——泛型 T: 'static 只是说 T 不含借用(能一直持有),别和「值真的活到程序结束」混为一谈。

单线程共享:Rc + RefCell(踩坑前置知识)

需要「多个所有者 + 运行期可变」时用 Rc(引用计数)与 RefCell(内部可变性,借用检查推迟到运行期):

rust
// 编译运行:rustc 1.98
use std::cell::RefCell;
use std::rc::Rc;

fn main() {
    let shared = Rc::new(RefCell::new(0));
    let a = Rc::clone(&shared); // 计数 +1,共享所有权
    let b = Rc::clone(&shared);

    *a.borrow_mut() += 1;       // 运行期可变借用(RefCell 内)
    *b.borrow_mut() += 1;
    println!("count = {}", shared.borrow()); // 2(同时存在多个不可变借用可读)
}

Rc 不是 Send,不能跨线程——多线程共享用 Arc(原子引用计数)+ Mutex(运行期互斥锁)。

踩坑记录

  • 移动后仍使用 → 编译错 E0382:重构思路是「传引用」或提前 clone(明确成本);
  • 想遍历 Vec<String> 又不想移动:用 &v(迭代 &String)或 v.iter(),别用 into_iter() 吞掉所有权;
  • 借用冲突几乎都可通过缩小借用作用域解决:先算完不可变借用、再改(利用 NLL);实在跨函数就用 RefCell/Mutex,但那是把编译期检查推迟到运行期,需警惕运行期 panic(borrow 冲突时);
  • &String 能传 &str 参数,但反过来不行&str 没有 String 的能力,需要时 .to_string()
  • 结构体/函数返回引用时生命周期标注报错:往往意味着「返回的引用不来自任何入参」(悬垂),得返回拥有所有权的类型(如 String)而不是 &str
  • clone 救急会掩盖所有权设计问题:数据流复杂时优先考虑重新设计为「数据一处拥有 + 各处借用」。

trait 与泛型系统

trait 定义与实现(语法要点)

trait 定义行为契约:类型显式 impl(与 Go 的隐式不同),可含默认方法。结构体/枚举为外部类型实现外部 trait 受孤儿规则限制(至少一方是本地类型)。

rust
// 编译运行:rustc 1.98
trait Greet {
    fn name(&self) -> &str;
    fn greet(&self) -> String {           // 默认方法:子类可覆写
        format!("你好,{}!", self.name())
    }
}

struct User { name: String }

impl Greet for User {
    fn name(&self) -> &str { &self.name } // 默认 greet 自动生效
}

fn main() {
    let u = User { name: "小明".into() };
    println!("{}", u.greet());            // 你好,小明!
}

常用派生宏(编译器生成实现):#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] 覆盖了绝大多数「样板实现」。

泛型与 trait bound(语法要点)

泛型函数与 impl:bounds 可用 T: Traitwhere 子句或 impl Trait 参数三种写法;dyn Trait 用于动态分发(运行时查 vtable,类似接口指针)。

rust
// 编译运行:rustc 1.98
trait Report {
    fn report(&self) -> String;
}

impl Report for i32 {
    fn report(&self) -> String { format!("值 {}", self) }
}

// 1) 泛型 + bound(编译期单态化,零开销)
fn print_r1<T: Report>(t: T) { println!("{}", t.report()); }
// 2) where 子句(多个 bound 更清晰)
fn print_r2<T>(t: T) where T: Report + Clone { println!("{}", t.report()); }
// 3) impl Trait 参数(语法糖,等价于泛型)
fn print_r3(t: impl Report) { println!("{}", t.report()); }
// 4) dyn Trait:运行期分发,接受任意实现了 Report 的引用
fn print_r4(t: &dyn Report) { println!("{}", t.report()); }

fn main() {
    let v = 42i32;
    print_r1(v);
    print_r2(v.clone());
    print_r3(v);
    print_r4(&v);
    // 三种写法的取舍:泛型体积大但零开销且可内联;dyn 体积小但每次调用间接跳转
}

关联类型与标准 trait(运行机制)

Iterator 是关联类型的典型:type Item 在实现时确定,便于链式 API 自然表达。

rust
// 编译运行:rustc 1.98
struct Evens { cur: i32 }

impl Iterator for Evens {
    type Item = i32;                       // 关联类型:每个迭代元素类型
    fn next(&mut self) -> Option<i32> {
        self.cur += 2;
        Some(self.cur)
    }
}

fn main() {
    let evens = Evens { cur: 0 };
    let first_three: Vec<i32> = evens.take(3).collect();
    println!("{:?}", first_three);         // [2, 4, 6]

    // 迭代器组合子链(惰性求值)
    let s: i32 = (1..=10).filter(|x| x % 2 == 0).sum();
    println!("{s}");                       // 30
}

标准库高频 trait 速查:

trait作用典型例子
Debug{:?} 调试输出#[derive(Debug)]println!("{:?}", v)
Display{} 用户可读输出自定义错误类型必须实现
From / Into低成本类型转换let s: String = "x".into();? 错误自动转换
Deref自动解引用&String → &str&Vec<T> → &[T]
AsRef参数多态(借用)fn read(p: impl AsRef<Path>)
Default零参构造vec![0; n] 相关、配置缺省值
Iterator / IntoIterator迭代与 forfor x in v 底层是 IntoIterator
Eq/PartialEq/Ord/Hash相等、排序、哈希HashMap 键、sort()、去重

对象安全与 dyn 的限制(踩坑必读)

只有对象安全的 trait 才能做成 dyn:不能有泛型方法,不能返回 Self(如 Clonefn clone(&self) -> Self 就做不了 dyn)。这是「Vec<Box<dyn Clone>> 为什么不行」的原因。

rust
// trait Clone { fn clone(&self) -> Self; }   // 返回 Self → 非对象安全
// let v: Vec<Box<dyn Clone>>;                 // ❌ 编译错:trait 非对象安全

替代:不用 dyn,直接 #[derive(Clone)] 后用泛型;或自定义返回 Box<dyn ...> 的对象安全 trait。

踩坑记录

  • 孤儿规则:想给 Vec<T> 实现自己的 trait?Vec 是外部类型、你的 trait 若是本地定义的可以;两边都外部则不行(用 newtype 包装绕开);
  • 泛型方法太多导致编译产物膨胀:性能分析时才需要关心,一般「泛型 + 内联」优先;
  • trait 方法名与 ToString/Display 冲突时用完全限定语法<Type as Trait>::method(&x)
  • 误以为 &dyn Traitimpl Trait 等价:前者堆/栈上放胖指针(数据指针 + vtable 指针)、运行期分发;后者编译期展开——类型不同不能互换,返回值位置只能写 implBox<dyn>impl Trait 不能用于返回值多类型分支);
  • derive Copy 但类型含 String → 编译错:先想清楚是不是真需要 Copy(能 Copy 的类型必须全字段 Copy);
  • Display 忘记实现 → {} 编译错;Debug 忘记 → {:?} 编译错:别把两者混写。

错误处理:Result / Option

Option:可选值(语法要点)

Option<T> = Some(T) | None。链式组合(map / and_then / unwrap_or)避免层层 match

rust
// 编译运行:rustc 1.98
fn main() {
    // 读取环境变量 PORT,解析失败或缺失都回退默认端口
    let port: u16 = std::env::var("PORT")          // Result<String, VarError>
        .ok()                                      // → Option<String>
        .and_then(|p| p.parse::<u16>().ok())       // 解析失败 → None
        .unwrap_or(8080);
    println!("port = {port}");

    let maybe: Option<i32> = None;
    let doubled = maybe.map(|x| x * 2);            // None 保持 None
    println!("{:?}", doubled);                     // None
}

unwrap/expect 失败即 panic:只在「错误不可能发生」或原型验证时使用。

Result:可恢复错误(语法要点 + 传播)

Result<T, E> = Ok(T) | Err(E)。函数内用 ? 传播错误(自动 From 转换),由调用方统一处理:

rust
// 编译运行:rustc 1.98
use std::fs;

/// 读取文件并解析首行为整数
fn first_line_number(path: &str) -> Result<i32, std::io::Error> {
    let content = fs::read_to_string(path)?;              // io::Error 直接返回
    let line = content.lines().next().ok_or_else(|| {
        std::io::Error::new(std::io::ErrorKind::InvalidData, "空文件")
    })?;
    line.trim()
        .parse()
        .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidData, "首行不是数字"))
}

自定义错误类型(工程实践)

领域错误收敛为枚举:实现 Display + std::error::Error,再用 #[from]? 自动转换。生产代码用 thiserror 派生(见第四节)减少样板:

rust
// 编译运行:rustc 1.98(标准库手写版)
use std::fmt;

#[derive(Debug)]
enum AppError {
    NotFound { id: u64 },
    Io(std::io::Error),
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            AppError::NotFound { id } => write!(f, "记录 {id} 不存在"),
            AppError::Io(e) => write!(f, "IO 错误: {e}"),
        }
    }
}

impl std::error::Error for AppError {} // 用源链支撑 {0}/source()

impl From<std::io::Error> for AppError { // ? 自动转换的桥梁
    fn from(e: std::io::Error) -> Self { AppError::Io(e) }
}

fn load(id: u64) -> Result<String, AppError> {
    // 假设此处发生 IO 错误 → ? 自动 From 成 AppError::Io
    let data = std::fs::read_to_string(format!("db/{id}.txt"))?;
    Ok(data)
}

fn main() -> Result<(), AppError> { // main 返回 Result:错误以 ExitCode 1 打印
    let data = load(1)?;
    println!("{data}");
    Ok(())
}

panic 与不可恢复错误(运行机制)

panic! 展开栈并清理(默认 unwind),可设 panic = "abort" 直接终止。RUST_BACKTRACE=1 打印调用栈。使用原则:

  • 契约破坏(如索引越界、unwrap 的前置条件被违反)→ panic 可接受;
  • 用户输入/网络/文件等外部可预期失败 → 必须走 Result
  • expect("信息")unwrap() 好:panic 信息自带上下文。

踩坑记录

  • 忽略 Result 编译器警告(#[must_use] 机制)不是 bug 但常是隐患:至少 let _ = ... 显式声明「我故意忽略」;
  • ? 自动 From 只对实现了 From 的方向生效:io::Error → 自定义错误需手动 impl From(或 thiserror#[from]);
  • 多层嵌套 Result<Result<...>>:用 .and_then 拍平,别用双重 ? 链硬解;
  • unwrap 大量散布在库代码里 = 未来的 panic 地雷:库 API 一律返回 Result,让调用方决定;
  • ?main 里返回 Box<dyn Error> 是最省事的脚手架写法,但要向上层「保真类型」时换具体错误枚举(见自定义错误示例);
  • matchErr(_) 吞掉错误细节 → 打日志时无信息可查:至少 dbg!/.inspect_err 先记录现场。

常用 crate 与 async 生态

工具链速查(语法要点)

命令作用常用组合
cargo new / init新项目/初始化--lib(库)、--vcs git
cargo add serde添加依赖默认最新版;--features--dev
cargo build / run构建/运行--release(优化)、--example x
cargo check只查类型(快)开发循环主力
cargo test跑测试#[test]cargo test -- --nocapture
cargo clippylint 加强版CI 必跑,-D warnings 当错误
cargo fmt格式化提交前必跑
cargo doc本地文档--open 浏览器打开
rustup toolchain/update工具链管理stable/nightly 切换

Cargo.toml 关键段:[dependencies][dev-dependencies](仅测试)、[features](可选编译特性)。二进制项目的 Cargo.lock 必须提交(锁定构建可复现);库项目通常忽略之。

常用 crate 速查表(工程选型)

领域crate说明
序列化serde + serde_jsonderive 宏驱动,JSON 事实标准
错误处理thiserror库错误枚举派生(#[error(...)]#[from]
错误聚合anyhow应用层 Result<T> = 任意错误(bail!context
CLIclap参数解析 + 帮助/补全(derive 模式)
HTTPreqwest同步/异步 HTTP 客户端
异步运行时tokioasync/await 运行时事实标准
并行计算rayon数据并行 par_iter(),一行把串行变并行
随机rand随机数/抽样
时间chrono(旧)time(新)日期时间
日志log + env_loggertracing结构化追踪
正则regex编译期缓存优化
一次性初始化once_cell / std::sync::OnceLock全局惰性变量

序列化与错误派生示例(工程样板)

rust
// 编译运行:需要 cargo add serde serde_json thiserror
use serde::{Deserialize, Serialize};
use thiserror::Error;

#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct Point { x: i32, y: i32 }

#[derive(Error, Debug)]
enum ApiError {
    #[error("资源 {id} 不存在")]
    NotFound { id: u64 },
    #[error(transparent)]
    Io(#[from] std::io::Error),        // ? 自动 From
    #[error(transparent)]
    Json(#[from] serde_json::Error),   // ? 自动 From
}

fn main() -> Result<(), ApiError> {
    let p = Point { x: 1, y: 2 };
    let json = serde_json::to_string(&p)?; // {"x":1,"y":2}
    println!("{json}");
    let back: Point = serde_json::from_str(&json)?;
    assert_eq!(p, back);
    Ok(())
}

async 生态入门(运行机制)

async fn 返回 Future,需运行时驱动(tokio)。#[tokio::main] 建运行时;spawn 并发执行;join! 并行等待多个 future;阻塞操作(文件 IO、CPU 密集)用 spawn_blocking,别堵住异步线程:

rust
// 编译运行:需要 cargo add tokio --features "macros,rt-multi-thread,time"
use std::time::Duration;
use tokio::time::sleep;

async fn fetch_one(id: u32) -> u32 {
    sleep(Duration::from_millis(id as u64 * 10)).await;
    id * 2
}

#[tokio::main]
async fn main() {
    // spawn:后台并发任务,await JoinHandle 取结果
    let h = tokio::spawn(async { fetch_one(1).await });
    let (a, b) = tokio::join!(fetch_one(2), fetch_one(3)); // 并行等待
    println!("{} {} {}", h.await.unwrap(), a, b);          // 2 4 6

    // 阻塞场景:CPU 密集任务放线程池,避免占死 worker
    let heavy = tokio::task::spawn_blocking(|| {
        let mut n = 0u64;
        for i in 1..=1_000_000 { n = n.wrapping_add(i); }
        n
    })
    .await
    .unwrap();
    println!("{heavy}");
}

async 编程口诀:等待用 await,并发用 join!/select!/spawn,阻塞用 spawn_blocking

踩坑记录

  • 忘配 tokio features:#[tokio::main] 报错 → 补 macros,rt-multi-thread
  • async fn 里同步阻塞(如 std::thread::sleep、大循环)会占死整个 worker 线程:换 sleep().awaitspawn_blocking
  • 依赖版本冲突(两个 crate 需要同一库不同版本):cargo update -p 包名 或检查重复;被 cargo tree 排查依赖链;
  • 默认特性带来无谓依赖:用 default-features = false + 显式 features 裁剪(如 tokio 只要 time);
  • Send/Sync 跨线程限制:Rc/RefCell/裸指针不能跨 spawn,报错指向「Future 不是 Send」时多半是捕获了非 Send 数据;
  • 忘了 Cargo.lock 提交 → 同事/CI 拿到不同版本依赖,偶现构建失败;
  • 库代码别用 anyhow(它吞掉具体类型),库的错误类型用 thiserror 枚举对外提供可匹配性。

状态与参考

  • 状态:已收录(2026-09-02 完成领域概览规划的 4 项主题)。
  • 运行环境:示例基于 rustc 1.98 / edition 2024(本机实测);第三方 crate 示例需 cargo add serde serde_json thiserrorcargo add tokio --features "macros,rt-multi-thread,time"
  • 参考Rust 程序设计语言(The Book)Rust by Examplestd 文档tokio 指南

下一步

  • [ ] 补充「unsafe 与 FFI」专题(裸指针、unsafe fn 约定、绑定 C 库)
  • [ ] 对照「语言对比」规划,沉淀 Rust 与 Go/JS/TS/Python 在所有权与并发上的差异笔记
  • [ ] 完善生态深入:tower/axum Web 服务、sqlx 数据库层的实战示例

写作规范与页面规划请参阅领域概览

基于 VitePress 构建 · 内容以知识共享方式沉淀