字
字节笔记本
2026年2月22日
Rust 学习笔记:从所有权到闭包的核心概念
这是一份 Rust 学习笔记,记录从所有权到闭包的核心概念,用简单的方式理解 Rust 的内存管理和类型系统。
一、所有权:值的生杀大权
核心问题
程序运行时,数据放在内存里。最核心的问题是:这块内存,什么时候能释放?谁来释放?
其他语言的解法:
- C/C++:程序员手动管理,容易出错(70% 的安全漏洞来源于此)
- Java/Python/Go:GC 垃圾回收,有性能损耗
- Rust:编译期通过规则推断,零开销且安全
三条核心规则
- 每个值只有一个所有者
- 所有者离开作用域,值被销毁
- 赋值或传参时,所有权转移(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 提供引用计数:
| 场景 | 多所有者 | 内部可变 |
|---|---|---|
| 单线程 | Rc | RefCell |
| 多线程 | Arc | Mutex / 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 离开作用域,锁自动释放,不用 unlockCow
懒复制,不需要修改时借用,需要修改时才复制。
八、错误处理
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 |
记住这三层,其他细节遇到再查。
分享: