编程入门
Java 入门教程:从零开始学 Java
零基础学 JavaScript 异步编程:从回调到 async/await
Rust 所有权与借用机制入门
Go 语言零基础教程 - 并发编程入门
Go 语言入门教程:并发编程基础(Goroutine 与 Channel)
Python 零基础入门:从第一行代码到独立编程
本站点使用 MrDoc 构建
-
+
Rust 所有权与借用机制入门
> Rust 之所以能在系统编程领域独树一帜,核心在于其**所有权(Ownership)**系统。它让 Rust 在不需要垃圾回收的情况下,依然能保证内存安全。本文将带你从零理解所有权、借用和生命周期三大核心概念。 ## 什么是所有权? 在 C/C++ 中,开发者需要手动管理内存(malloc/free);在 Java/Python 中,垃圾回收器(GC)自动处理。Rust 选择了第三条路:**编译期内存管理**,通过所有权系统在编译阶段就确定每块内存的释放时机。  Rust 的所有权遵循三条基本规则: 1. **每个值有且只有一个所有者(Owner)**:变量绑定就是所有者,值在同一时刻只能被一个变量拥有。 2. **所有者离开作用域时,值被自动释放(Drop)**:无需手动 free,编译器在作用域结尾自动插入 drop 调用。 3. **赋值操作会转移所有权(Move 语义)**:与 Copy 语义不同,默认情况下赋值会让原变量失效。 ### 代码示例:所有权转移 ```rust fn main() { let s1 = String::from("hello"); let s2 = s1; // 所有权从 s1 转移到 s2,s1 不再有效 // println!("{}", s1); // ❌ 编译错误!s1 已经被 move println!("{}", s2); // ✅ 正常使用 s2 } ``` 上面的代码中,`s1` 赋值给 `s2` 后,`s1` 就失效了。这是 Rust 防止「双重释放」问题的机制——如果两个变量同时指向同一块堆内存,释放时就会出问题。 ### Clone:深拷贝 如果你确实需要复制一份独立的数据,可以使用 `.clone()`: ```rust let s1 = String::from("hello"); let s2 = s1.clone(); // 深拷贝,s1 和 s2 各自独立 println!("s1 = {}, s2 = {}", s1, s2); // ✅ 两者都有效 ``` ### 栈上的 Copy 类型 基本类型(i32、f64、bool、char 等)存储在栈上,赋值时会自动复制,不涉及所有权转移: ```rust let x: i32 = 42; let y = x; // Copy 语义,x 依然有效 println!("x = {}, y = {}", x, y); // ✅ 都能使用 ``` ## 引用与借用 如果每次传参都要转移所有权,那函数调用后就拿不回数据了。Rust 提供了**引用(Reference)**机制,允许你「借用」数据而不获取所有权,这称为**借用(Borrowing)**。  ### 不可变借用 &T ```rust fn calculate_length(s: &String) -> usize { s.len() // 借用 s,不获取所有权 } fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); // 传入引用 // s1 仍然有效,因为它只是被借出 println!("'{}' 的长度是 {}", s1, len); } ``` ### 可变借用 &mut T 有时你需要修改借来的数据,这时用 `&mut`: ```rust fn append_world(s: &mut String) { s.push_str(", world"); } fn main() { let mut s = String::from("hello"); append_world(&mut s); println!("{}", s); // 输出: hello, world } ``` ### 借用的核心限制 Rust 编译器强制执行以下规则,从根源上杜绝数据竞争: - **在同一时刻,要么有任意多个不可变引用 `&T`,要么只能有一个可变引用 `&mut T`,二者不可共存。** - **引用必须始终有效**(不能出现悬垂引用)。 ```rust let mut s = String::from("hello"); let r1 = &s; // ✅ 不可变借用 let r2 = &s; // ✅ 再来一个不可变借用 // let r3 = &mut s; // ❌ 不能同时有可变借用和不可变借用 println!("{} {}", r1, r2); // r1、r2 最后使用后,借用结束 let r3 = &mut s; // ✅ 现在可以可变借用了(r1、r2 生命周期已结束) println!("{}", r3); ``` Rust 的 **NLL(Non-Lexical Lifetimes)** 机制让引用的作用域在实际最后一次使用时结束,而非严格到花括号结尾,这让代码更灵活。 ## 生命周期(Lifetime) 当函数返回引用时,编译器需要确保引用指向的数据仍然有效。**生命周期标注**就是帮助编译器理解引用之间关系的工具。  ### 为什么需要生命周期? ```rust // ❌ 编译器不知道返回的引用来自 x 还是 y fn longest(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y } } ``` 上面的代码会编译失败,因为返回值的生命周期不确定。我们需要显式标注: ```rust fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } fn main() { let string1 = String::from("long string"); let result; { let string2 = String::from("xyz"); result = longest(string1.as_str(), string2.as_str()); println!("最长的字符串是 {}", result); // ✅ string2 还在作用域内 } // println!("{}", result); // ❌ string2 已被 drop,result 可能悬垂 } ``` `'a` 是生命周期参数,它告诉编译器:返回值的生命周期与两个参数中较短的那个一致。 ### 生命周期省略规则 在日常编码中,你不需要每次都标注生命周期。Rust 有三条省略规则(Elision Rules): 1. 每个引用参数都有自己的生命周期参数。 2. 如果只有一个输入生命周期参数,它被赋给所有输出生命周期参数。 3. 如果有多个输入生命周期但有 `&self`/`&mut self`,`self` 的生命周期赋给输出。 ```rust // 不需要手动标注,编译器自动推导 fn first_word(s: &str) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] } ``` ## 结构体中的生命周期 当结构体持有引用时,必须标注生命周期: ```rust struct ImportantExcerpt<'a> { part: &'a str, } fn main() { let novel = String::from("Call me Ishmael. Some years ago..."); let first_sentence = novel.split('.').next().unwrap(); let excerpt = ImportantExcerpt { part: first_sentence, // excerpt 不能活得比 novel 更久 }; println!("{}", excerpt.part); } ``` ## 静态生命周期 `'static` `'static` 表示引用在整个程序运行期间都有效。所有字符串字面量都拥有 `'static` 生命周期: ```rust let s: &'static str = "I live forever!"; ``` > 注意:不要为了解决编译错误就随意给引用加 `'static`,这通常意味着设计有问题。优先考虑重构代码结构。 ## 小结 | 概念 | 核心规则 | 关键字 | |------|---------|--------| | 所有权 | 每个值只有一个所有者,离开作用域自动释放 | move, drop, clone | | 不可变借用 | 允许多个同时存在的 `&T` | `&` | | 可变借用 | 同一时刻只允许一个 `&mut T` | `&mut` | | 生命周期 | 确保引用始终有效,防止悬垂引用 | `'a`, `'static` | Rust 的所有权系统看起来限制很多,但正是这些限制让你在编译期就能发现大部分内存安全问题。一旦习惯了这个思维模型,你会发现它让代码更健壮、更易推理。 > 下一篇我们将学习 Rust 的错误处理机制——`Result` 与 `Option`,敬请期待!
admin
2026年5月17日 00:07
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
PDF文档(打印)
分享
链接
类型
密码
更新密码