Rust学习笔记:所有权、借用与生命周期——Rust的核心机制

如果前面三篇是 Rust 的「皮肉」,这一篇就是「骨架」。所有权(Ownership)、借用(Borrowing)和生命周期(Lifetimes)是 Rust 区别于其他所有主流语言的独有机制。它们让 Rust 在不依赖垃圾回收的前提下实现内存安全------没有空指针、没有悬垂引用、没有双重释放。

初次接触可能会觉得反直觉,但一旦理解,你会发现这些规则不是限制,而是编译器在帮你提前排除 bug。

本文所有代码均已实测通过。


1. 所有权:谁拥有,谁负责

Rust 的所有权系统只有三条规则:

  1. Rust 中每一个值都有一个所有者(owner)
  2. 同一时间只有一个所有者
  3. 当所有者离开作用域,值被自动释放(drop)

听上去简单,但落实到代码里会有一系列「意料之外」的行为。

1.1 String 的移动

String 类型的数据存在堆上。把一个 String 赋给另一个变量时,所有权会转移------旧变量失效:

rust 复制代码
let s1 = String::from("hello");
let s2 = s1;
// println!("{s1}"); // ❌ s1 已失效
println!("{s2}");     // ✅ s2 有效

这和其他语言完全不同。在 Python 或 Java 里,s2 = s1 之后两个变量都能用。Rust 选择了「移动」而非「浅拷贝」,避免了堆上同一块内存被释放两次的问题。

1.2 栈上数据的 Copy

标量类型(i32f64boolchar 等)存在栈上,大小在编译期已知。它们实现了 Copy trait,赋值时自动复制而不是移动:

rust 复制代码
let x = 5;
let y = x;
println!("x = {x}, y = {y}"); // ✅ 两个都能用

数组和元组如果全部元素都实现了 Copy,它们自己也自动实现 Copy

rust 复制代码
let arr = [1, 2, 3];
let arr2 = arr;
println!("{arr:?}, {arr2:?}"); // ✅ 都能用

简单记:需要分配堆内存的类型(StringVec 等)赋值会移动;栈上类型自动 Copy。

1.3 函数传参同样转移所有权

rust 复制代码
fn take_ownership(s: String) {
    println!("接收: {s}");
} // s 离开作用域,被释放

let s = String::from("world");
take_ownership(s);
// println!("{s}"); // ❌ s 所有权已转移

函数参数和赋值遵循相同的所有权规则。把一个值传给函数,所有权就归函数了。


2. Clone:显式深拷贝

如果确实需要一份独立的数据副本,用 .clone()

rust 复制代码
let s1 = String::from("hello");
let s2 = s1.clone(); // 在堆上复制一份新数据
println!("s1 = {s1}, s2 = {s2}"); // ✅ 两个独立拥有

Rust 的设计哲学是:昂贵的操作要显式写出来.clone().copy() 的区别就在于此------Copy 是编译器自动做的、零成本的;Clone 是程序员手动触发的、可能有开销的。


3. 借用:不拿所有权的访问

如果每次传参都把所有权交出去,代码会极其繁琐。所以才有了引用(Reference)------借用值但不拥有它。

3.1 不可变借用 &T

rust 复制代码
fn calc_len(s: &String) -> usize {
    s.len() // 借读,不拿所有权
}

let s = String::from("hello");
let len = calc_len(&s);  // &s 创建引用
println!("'{s}' 的长度是 {len}");
println!("s 仍然可用: {s}"); // ✅ 所有权没丢

&s 创建一个指向 s 的引用,把引用传给函数。函数结束时引用失效,但 s 本身完好。这叫借用------你借给别人看,看完还你。

3.2 可变借用 &mut T

rust 复制代码
fn append_world(s: &mut String) {
    s.push_str(", world!");
}

let mut s = String::from("hello");
append_world(&mut s);
println!("修改后: {s}"); // hello, world!

4. 借用规则(牢记)

Rust 的借用检查器(Borrow Checker)遵循两条硬规则:

规则一:同一时间,只能有一个可变引用,或者任意多个不可变引用------二者不能共存。

rust 复制代码
let mut s = String::from("hello");

let r1 = &s;     // 不可变引用
let r2 = &s;     // 多个不可变引用 OK
println!("{r1}, {r2}");

// r1、r2 最后一次使用后,可以创建可变引用
let r3 = &mut s;
r3.push_str("!");
println!("{r3}");

规则二:引用必须始终有效------不能有悬垂引用。

rust 复制代码
// fn dangle() -> &String {
//     let s = String::from("hello");
//     &s  // ❌ s 离开作用域被释放,返回的引用指向无效内存
// }

这两条规则在编译期就排除了数据竞争(data race)的可能------多线程下最常见的并发 bug,Rust 直接从语言层面消灭了。


5. 生命周期:引用活多久

生命周期(lifetime)是编译器用来确保「引用不会活得过它们的值」的标记。

5.1 为什么需要生命周期标注

看这个函数:

rust 复制代码
fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() { x } else { y }
}

编译器不知道返回值引用的是 x 还是 y,也就无法判断返回的引用是否有效。这时需要生命周期标注:

rust 复制代码
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

'a 的意思是:返回值的引用有效期 = xy较短的那个。如果传进来的两个参数生命周期不一致,编译器会用较短的那个来约束返回值:

rust 复制代码
let s1 = String::from("long");
{
    let s2 = String::from("short");
    let result = longest(&s1, &s2);
    println!("{result}"); // ✅ 在这个作用域内 OK
}
// println!("{result}"); // ❌ s2 已释放,result 也跟着失效

5.2 初学者只需要知道

  • 'a 是生命周期标注,不会改变代码的运行时行为,只给编译器看
  • 大部分场景下 Rust 可以自动推断生命周期(生命周期省略规则),只有函数返回引用且参数有多个引用时才需要手动标注
  • 现阶段先记住这个用法,后续写项目遇到编译器提示再回来加深理解

6. 一张图总结

操作 所有权变化 之后原变量还能用?
let y = x(x 是 i32 Copy
let y = x(x 是 String Move
x.clone() 深拷贝
&x 不可变借用
&mut x 可变借用 ✅(但借用期间 x 被锁定)

总结

所有权、借用和生命周期是 Rust 最难但也最重要的三个概念。核心要点:

  1. 所有权:每个值只有一个所有者,离开作用域自动释放。堆数据赋值会移动所有权
  2. Copy vs Clone:Copy 是自动的零成本复制(栈上数据),Clone 是显式的深拷贝(堆上数据)
  3. 借用&T 只读不拥有,&mut T 可写不拥有。同一时间要么一个可变借用,要么多个不可变借用
  4. 生命周期 :编译器确保引用不会活得过它们指向的值。大多数情况自动推断,函数返回引用时才需要 'a 标注

理解这些规则后,Rust 编译器就不再是你的对手,而是最严格的队友------它在编译期就把你未来会犯的内存错误全拦住了。

下一篇进入结构体与枚举,开始用 Rust 组织真正的业务数据。

相关推荐
蜗牛互联网1 小时前
语音AI开始边听边说,改变的不只是响应速度
java·人工智能·后端·语音识别
leoZ2311 小时前
2026-09-10-静态扫描连错三次-用运行环境当裁判
前端·javascript·vue.js·人工智能·opencv·机器学习·数据挖掘
Maxkim1 小时前
我把一个"会自己查资料"的 AI 助手塞进了浏览器侧边栏,开源了
前端·javascript
Mav1 小时前
[个人学习记录]从零构建高性能 LLM 推理网关:Go 语言 SSE 流式转发、级联取消与背压
后端
狼爷1 小时前
磁盘IO打满怎么办?我用5个真实案例,总结了这套可复用的排查方法论
后端·性能优化
计算机魔术师2 小时前
英伟达要投100亿美元入股Anthropic,史上最大IPO的底牌
前端
小羊在睡觉2 小时前
HLS视频
linux·后端·golang
苍何2 小时前
小米版 Codex,干活有点猛啊
后端
yume_sibai2 小时前
02-Flutter进阶开发
前端·flutter