# Rust 所有权(Ownership)概念详解 > 所有权是 Rust 最独特的特性,它让 Rust 无需垃圾回收器就能保证内存安全。 ## 目录 - [1. 什么是所有权](#1-什么是所有权) - [2. 所有权规则](#2-所有权规则) - [3. 变量作用域](#3-变量作用域) - [4. String 类型与内存分配](#4-string-类型与内存分配) - [5. 移动(Move)](#5-移动move) - [6. 克隆(Clone)](#6-克隆clone) - [7. Copy 特征](#7-copy-特征) - [8. 所有权与函数](#8-所有权与函数) - [9. 返回值与作用域](#9-返回值与作用域) - [10. 引用与借用](#10-引用与借用) - [11. 可变引用](#11-可变引用) - [12. 悬垂引用](#12-悬垂引用) - [13. 切片类型](#13-切片类型) - [14. 总结](#14-总结) --- ## 1. 什么是所有权 所有权是一套用于管理内存的规则。在大部分编程语言中,内存管理通过以下方式处理: - **手动管理**(如 C/C++):程序员显式分配和释放内存 - **垃圾回收**(如 Java/Go):运行时自动回收不再使用的内存 - **所有权系统**(Rust):通过编译时检查的一套规则管理内存 ### 为什么需要所有权? - 无需垃圾回收,性能更高 - 无需手动管理内存,更安全 - 在编译期消除内存错误 --- ## 2. 所有权规则 三条核心规则: 1. Rust 中的**每一个值**都有一个**所有者**(owner) 2. 值在任一时刻**有且仅有一个**所有者 3. 当所有者离开作用域,值会被**自动释放**(调用 `drop`) --- ## 3. 变量作用域 ```rust fn main() { // s 在这里无效,尚未声明 let s = "hello"; // s 从此处开始有效 // 可以使用 s println!("{}", s); } // 此作用域结束,s 不再有效 ``` --- ## 4. String 类型与内存分配 字符串字面量 `"hello"` 是不可变的,编译时已知,存储在二进制文件中。对于在运行时可变的字符串,使用 `String` 类型。 ```rust fn main() { let mut s = String::from("hello"); s.push_str(", world!"); // push_str() 追加字符串 println!("{}", s); // 输出: hello, world! } ``` ### 内存分配与释放 `String` 类型的数据存储在**堆**上: - 栈:存储指针、长度、容量(固定大小,编译时已知) - 堆:存储实际的字符串内容(编译时大小未知,运行时动态分配) ```text 栈 堆 ┌──────────┐ ┌──────────┐ │ ptr │────────>│ h e l l o│ │ len: 5 │ │ w o r l d│ │ cap: 5 │ └──────────┘ └──────────┘ ``` 当变量离开作用域时,Rust 自动调用 `drop` 函数,将堆内存归还给操作系统。 --- ## 5. 移动(Move) ```rust let x = 5; let y = x; // x 的值被复制到 y(栈上数据,Copy 类型) println!("{}", x); // ✅ 正常工作,x 仍可用 let s1 = String::from("hello"); let s2 = s1; // s1 的所有权**移动**到了 s2 // println!("{}", s1); // ❌ 编译错误!s1 已失效 println!("{}", s2); // ✅ 正常工作 ``` ### 为什么需要移动? 如果 Rust 对堆数据也做浅拷贝,当 `s1` 和 `s2` 同时离开作用域时,它们都会尝试释放同一块堆内存,导致**二次释放**错误。 Rust 通过**移动**解决了这个问题:赋值后旧变量失效,只有一个所有者负责释放内存。 ```text 移动前: 移动后: s1 → [堆内存] s2 → [堆内存] s1 已失效 ❌ ``` --- ## 6. 克隆(Clone) 如果确实需要深度复制堆数据,使用 `clone` 方法: ```rust let s1 = String::from("hello"); let s2 = s1.clone(); // 在堆上创建一份完整副本 println!("s1 = {}, s2 = {}", s1, s2); // ✅ 两者都可用 ``` ```text 克隆后: s1 → [堆内存1: "hello"] s2 → [堆内存2: "hello"] ``` - `clone` 会执行**深拷贝**,包括堆数据 - 代价较高,仅在确实需要时使用 --- ## 7. Copy 特征 对于**完全存储在栈上**的数据类型,Rust 提供了 `Copy` 特征: ```rust let x = 5; let y = x; // x 被复制,而非移动 println!("{}", x); // ✅ 仍可使用 ``` ### 具有 Copy 的类型 - 所有整数类型:`u32`、`i64` 等 - 所有浮点类型:`f32`、`f64` - 布尔类型:`bool` - 字符类型:`char` - 元组(仅当所有元素都实现了 Copy):`(i32, bool)` ✅,`(i32, String)` ❌ ### 不具有 Copy 的类型 - `String` - `Vec` - 任何实现了 `Drop` trait 的类型 --- ## 8. 所有权与函数 将值传递给函数时,会发生**移动**或**复制**,规则与赋值相同: ```rust fn main() { let s = String::from("hello"); takes_ownership(s); // s 的所有权移动到函数内 // println!("{}", s); // ❌ 编译错误!s 已失效 let x = 5; makes_copy(x); // x 被复制到函数内(i32 是 Copy 类型) println!("{}", x); // ✅ x 仍然可用 } // x 离开作用域,然后 s 离开作用域(但 s 已被移动,不会发生什么) fn takes_ownership(some_string: String) { println!("{}", some_string); } // some_string 离开作用域,drop 被调用,内存释放 fn makes_copy(some_integer: i32) { println!("{}", some_integer); } // some_integer 离开作用域,无特殊操作(栈数据) ``` --- ## 9. 返回值与作用域 函数的返回值也可以**转移所有权**: ```rust fn main() { let s1 = gives_ownership(); // 返回值将所有权移给 s1 let s2 = String::from("hello"); let s3 = takes_and_gives_back(s2); // s2 移入函数,函数返回后又移给 s3 // println!("{}", s2); // ❌ s2 已失效 println!("s1 = {}, s3 = {}", s1, s3); } // s1、s3 离开作用域并被释放;s2 已被移动,不触发释放 fn gives_ownership() -> String { let some_string = String::from("hello"); some_string // 返回 some_string,所有权移出 } fn takes_and_gives_back(a_string: String) -> String { a_string // 返回 a_string,所有权移出 } ``` ### 元组返回多个值 ```rust fn main() { let s1 = String::from("hello"); let (s2, len) = calculate_length(s1); println!("'{}' 的长度是 {}", s2, len); } fn calculate_length(s: String) -> (String, usize) { let length = s.len(); (s, length) // 返回字符串和它的长度 } ``` 这种写法有些繁琐,Rust 提供了**引用**来简化。 --- ## 10. 引用与借用 **引用**允许在不获取所有权的情况下使用值。 ```rust fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); // 传递引用,不转移所有权 println!("'{}' 的长度是 {}", s1, len); // ✅ s1 仍可使用 } fn calculate_length(s: &String) -> usize { // s 是对 String 的引用 s.len() } // s 离开作用域,但由于它不拥有值的所有权,不会调用 drop ``` ```text s1 → [堆内存: "hello"] ↑ &s1(引用,指向 s1 的值) ``` ### 引用的规则 1. 引用**不获取**所有权 2. 创建引用的行为称为**借用**(borrowing) 3. 借用者离开作用域时,不会释放底层数据 --- ## 11. 可变引用 默认情况下,引用是**不可变的**。要修改借用的值,需要使用**可变引用**: ```rust fn main() { let mut s = String::from("hello"); change(&mut s); // 传递可变引用 println!("{}", s); // 输出:hello, world! } fn change(some_string: &mut String) { some_string.push_str(", world!"); } ``` ### 可变引用的限制 **在任意给定时间,只能拥有以下两者之一:** - 一个可变引用 - 任意数量的不可变引用 ```rust let mut s = String::from("hello"); // ✅ 不可变引用可以同时存在多个 let r1 = &s; let r2 = &s; println!("{} and {}", r1, r2); // ✅ 不可变引用不再使用后,可以创建可变引用 let r3 = &mut s; println!("{}", r3); ``` ```rust let mut s = String::from("hello"); let r1 = &s; // 不可变借用 let r2 = &s; // 不可变借用 let r3 = &mut s; // ❌ 编译错误!不能同时存在不可变和可变引用 println!("{}, {}, {}", r1, r2, r3); ``` ```rust let mut s = String::from("hello"); let r1 = &mut s; let r2 = &mut s; // ❌ 编译错误!不能同时存在两个可变引用 println!("{}, {}", r1, r2); ``` **这样设计的好处:在编译时防止数据竞争!** > 数据竞争发生的三个条件: > 1. 两个或多个指针同时访问同一数据 > 2. 至少有一个指针用于写入 > 3. 没有同步数据访问的机制 Rust 的规则直接在编译期消灭了数据竞争。 --- ## 12. 悬垂引用 **悬垂引用**是指向已被释放内存的指针。Rust 编译器保证永远不会产生悬垂引用: ```rust fn main() { let reference_to_nothing = dangle(); // ❌ 编译错误! } fn dangle() -> &String { let s = String::from("hello"); &s // 返回 s 的引用 } // s 离开作用域,内存被释放;但返回的引用仍指向已释放的内存 ``` 编译器错误信息: ``` error[E0106]: missing lifetime specifier | | fn dangle() -> &String { | ^ expected named lifetime parameter ``` 正确的做法是直接返回 `String`,将所有权移出去: ```rust fn no_dangle() -> String { let s = String::from("hello"); s // 所有权移出 } ``` --- ## 13. 切片类型 **切片**是一种不获取所有权的引用,允许引用集合中一段连续的元素。 ### 字符串切片 ```rust let s = String::from("hello world"); let hello = &s[0..5]; // "hello" let world = &s[6..11]; // "world" ``` 语法简写: ```rust let s = String::from("hello world"); let hello = &s[..5]; // 从 0 开始 let world = &s[6..]; // 到末尾 let whole = &s[..]; // 整个字符串 ``` ### 字符串字面量就是切片 ```rust let s = "Hello, world!"; // s 的类型是 &str,即字符串切片 ``` ### 其他切片 ```rust let a = [1, 2, 3, 4, 5]; let slice = &a[1..3]; // 类型是 &[i32],包含 [2, 3] assert_eq!(slice, &[2, 3]); ``` --- ## 14. 总结 | 概念 | 说明 | |------|------| | **所有权** | 每个值有且仅有一个所有者;所有者离开作用域时值被释放 | | **移动** | 赋值/传参时转移所有权,原变量失效 | | **克隆** | 深拷贝堆数据,两个变量独立 | | **Copy** | 栈上数据的自动复制(整数、布尔等基础类型) | | **引用** | 不获取所有权的指针,允许多个存在 | | **可变引用** | 可修改数据的引用,同时只能有一个 | | **切片** | 对集合局部元素的引用 | | **借用规则** | 任意时刻:要么一个可变引用,要么多个不可变引用 | ### 借用检查器保证了 - **内存安全**:无悬垂指针、无二次释放、无使用后释放 - **线程安全**:无数据竞争 - **零运行时开销**:所有检查在编译时完成 > 所有权系统可能会让初学者感到"Rust 编译器在跟你作对",但请记住:**编译器不是在刁难你,而是在保护你写出的每一行代码都是内存安全的。** 当你熟悉所有权之后,这些规则会变得自然且合理。