字节笔记本

2026年2月22日

Rust 学习笔记:从所有权到闭包的核心概念

这是一份 Rust 学习笔记,记录从所有权到闭包的核心概念,用简单的方式理解 Rust 的内存管理和类型系统。

一、所有权:值的生杀大权

核心问题

程序运行时,数据放在内存里。最核心的问题是:这块内存,什么时候能释放?谁来释放?

其他语言的解法:

  • C/C++:程序员手动管理,容易出错(70% 的安全漏洞来源于此)
  • Java/Python/Go:GC 垃圾回收,有性能损耗
  • Rust:编译期通过规则推断,零开销且安全

三条核心规则

  1. 每个值只有一个所有者
  2. 所有者离开作用域,值被销毁
  3. 赋值或传参时,所有权转移(Move),原变量不能再用
rust
let s = String::from("hello");
let s2 = s;  // 所有权转移给 s2,s 失效
// println!("{}", s);  // 报错!s 已经不能用了
println!("{}", s2);  // 正常

Copy 语义

简单类型(整数、布尔、字符等)赋值时自动复制,原变量仍可用:

rust
let n = 42;
let n2 = n;  // 复制了一份,n 还能用
println!("{} {}", n, n2);  // 两个都正常

为什么整数可以?因为大小固定,复制代价极低。String 的数据在堆上,不能随便复制。

二、借用:不转移所有权

不想转移所有权,只是借用一下,加 & 符号:

rust
let s = String::from("hello");
let len = calculate_len(&s);  // 借给函数用
println!("{} 长度是 {}", s, len);  // s 还能用

fn calculate_len(s: &String) -> usize {
    s.len()
}  // 借用归还,所有权没有变

借用规则(类似读写锁)

  • 只读借用 &:可以同时多个
  • 可变借用 &mut:同一时刻只能一个,且和只读借用互斥
rust
let mut s = String::from("hello");
change(&mut s);  // 可变借用,可以修改

fn change(s: &mut String) {
    s.push_str(" world");
}

生命周期约束

借用的生命周期不能超过值本身,否则就是访问已释放的内存。

三、多所有者:Rc 和 Arc

有些场景必须共享所有权(DAG、多线程),Rust 提供引用计数:

场景多所有者内部可变
单线程RcRefCell
多线程ArcMutex / RwLock
rust
use std::rc::Rc;

let a = Rc::new(String::from("shared"));
let b = a.clone();  // 引用计数 +1,数据没复制
let c = a.clone();  // 引用计数变成 3

println!("{} {} {}", a, b, c);  // 三个都能用
// 计数归零时才真正释放内存

四、生命周期:告诉编译器引用关系

为什么需要标注?

编译器看不到调用者的上下文,当函数有多个引用参数时,它不知道返回值的生命周期跟哪个参数绑定:

rust
// 编译器懵了:返回值跟 s1 有关,还是跟 s2 有关?
fn max(s1: &str, s2: &str) -> &str { ... }

// 加上标注,告诉编译器约束关系
fn max<'a>(s1: &'a str, s2: &'a str) -> &'a str { ... }

自动推导规则

大多数情况下编译器自动处理,无法推断时才需要手动标注。只需确定:返回值和哪个参数的生命周期相关

五、类型系统与泛型

类型推导

rust
let mut map = BTreeMap::new();
map.insert("hello", "world");
// 编译器自动知道 map 是 BTreeMap<&str, &str>

泛型

同一段逻辑,支持不同类型:

rust
fn id<T>(x: T) -> T {
    x
}

id(42);      // 整数
id("hello"); // 字符串

mut 关键字

Rust 默认变量不可变,加 mut 才能修改:

rust
let x = 5;
// x = 6; // 报错!

let mut y = 5;
y = 6; // 正常

六、Trait:定义"能做什么"

Trait 就是接口,定义约定,然后让不同类型各自实现:

rust
// 定义约定
trait Animal {
    fn name(&self) -> &str;
}

// Cat 实现约定
struct Cat;
impl Animal for Cat {
    fn name(&self) -> &str {
        "猫"
    }
}

常用 Trait

Trait作用
Clone深拷贝
Copy浅拷贝(栈上数据)
Drop值销毁时自动调用
Debug给开发者看,{:?} 打印
Display给用户看,{} 打印
Default提供缺省值

七、智能指针

比普通指针多了额外能力的数据结构,核心特点是用完自动收拾残局

Box

把数据放到堆上,离开作用域自动释放:

rust
let b = Box::new(5); // 5 存在堆上
// b 离开作用域,堆内存自动释放

MutexGuard

拿到锁,用完自动释放:

rust
let g = mutex.lock().unwrap(); // 拿到锁
// ... 做事
// g 离开作用域,锁自动释放,不用 unlock

Cow

懒复制,不需要修改时借用,需要修改时才复制。

八、错误处理

Rust 用类型强制你处理错误,不处理就编译不过。

Option:有值或没值

rust
let x: Option<i32> = Some(5);  // 有值
let y: Option<i32> = None;     // 没值

Result:成功或失败

rust
let x: Result<i32, String> = Ok(5);              // 成功
let y: Result<i32, String> = Err("出错了".to_string()); // 失败

? 操作符

懒得处理就往上传:

rust
fn read_file() -> Result<String, Error> {
    let mut f = File::open("test.txt")?; // 出错直接返回
    // ...
}

九、闭包

能捕获周围变量的匿名函数:

rust
let name = String::from("张三");
let say_hello = || println!("你好, {}", name);
say_hello(); // 输出:你好, 张三

三种闭包类型

  • FnOnce:只能调用一次(把捕获的变量移走了)
  • FnMut:可以多次调用,但会修改捕获的变量
  • Fn:可以多次调用,只读不改

十、一句话总结

Rust 用单一所有权 + 借用规则 + 生命周期这套编译期检查机制,在不引入 GC 的前提下,保证了内存和资源的安全管理。

代价是写代码时需要遵守更严格的规则,收益是运行时零开销且没有内存安全漏洞。

三层核心对应关系

场景解决方案符号
只用一次,直接传所有权转移
只是看看,不想失效借用&
多处都要用引用计数Rc / Arc

记住这三层,其他细节遇到再查。

分享: