强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Rust 系统编程语言完全教程 / 第06章:所有权系统

第06章:所有权系统

所有权系统是 Rust 最核心、最独特的特性。理解所有权是学好 Rust 的关键。

6.1 什么是所有权

栈与堆

特性栈(Stack)堆(Heap)
分配速度极快(移动栈指针)较慢(需查找空闲空间)
大小编译时确定运行时确定
生命周期作用域结束自动释放需要手动或自动管理
典型数据整数、浮点、布尔、指针String、Vec、Box

所有权三条规则

fn main() {
    // 规则1:每个值有且仅有一个所有者
    let s = String::from("hello");

    // 规则2:同一时间只能有一个所有者
    let s2 = s; // s 的所有权移动(move)到 s2

    // println!("{}", s); // ❌ s 已失效
    println!("{}", s2);   // ✅ s2 是当前所有者

    // 规则3:当所有者离开作用域,值被自动释放
} // s2 离开作用域,String 被释放(调用 drop)

6.2 移动语义(Move)

String 的移动

fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // 移动(move)

    // s1 不再有效,不能再使用
    // println!("{}", s1); // ❌ 编译错误

    println!("{}", s2); // ✅
}

内存图示:

移动前:
  s1 → [ptr|len|cap] → 堆: "hello"
  s2 (未初始化)

移动后:
  s1 (已失效)
  s2 → [ptr|len|cap] → 堆: "hello"

Copy 类型(复制而非移动)

实现了 Copy trait 的类型在赋值时会复制而非移动:

fn main() {
    let x = 5;
    let y = x; // 复制(copy)

    // 两个都有效
    println!("x={}, y={}", x, y); // ✅

    // Copy 类型包括:整数、浮点、布尔、字符、只包含 Copy 类型的元组
}
类型移动/复制说明
i32, f64, bool, charCopy栈上数据,复制成本低
(i32, f64)Copy只含 Copy 类型的元组
StringMove堆上数据,移动避免双重释放
Vec<T>Move堆上数据
&T, &mut TCopy引用本身是复制的
Box<T>Move堆上数据

6.3 克隆(Clone)

如果需要深拷贝堆上数据,使用 clone()

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone(); // 深拷贝

    // 两个都有效且独立
    println!("s1={}, s2={}", s1, s2); // ✅

    // 修改 s2 不影响 s1
    let mut s2 = s2;
    s2.push_str(" world");
    println!("s1={}, s2={}", s1, s2); // s1="hello", s2="hello world"
}

注意: clone() 可能很昂贵(需要分配内存并复制数据),只在确实需要独立副本时使用。


6.4 函数与所有权

传递所有权

fn takes_ownership(s: String) {
    println!("获得所有权: {}", s);
} // s 被释放

fn makes_copy(n: i32) {
    println!("复制的值: {}", n);
} // n 被释放,但原始值不受影响

fn main() {
    let s = String::from("hello");
    takes_ownership(s);
    // println!("{}", s); // ❌ s 的所有权已移动到函数中

    let x = 5;
    makes_copy(x);
    println!("x 仍然有效: {}", x); // ✅ x 是 Copy 类型
}

返回所有权

fn gives_ownership() -> String {
    let s = String::from("hello");
    s // 返回时移动所有权
}

fn takes_and_gives_back(s: String) -> String {
    println!("临时获得: {}", s);
    s // 返回时归还所有权
}

fn main() {
    let s1 = gives_ownership();
    println!("s1 = {}", s1);

    let s2 = takes_and_gives_back(s1);
    // println!("{}", s1); // ❌ s1 已移动
    println!("s2 = {}", s2); // ✅
}

6.5 引用与借用(Reference & Borrowing)

不可变引用(&T)

fn calculate_length(s: &String) -> usize {
    s.len()
} // s 是引用,离开作用域不会释放原始数据

fn main() {
    let s = String::from("hello");
    let len = calculate_length(&s); // 借用 s
    println!("\"{}\" 的长度是 {}", s, len); // ✅ s 仍然有效
}

可变引用(&mut T)

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!
}

借用规则详解

fn main() {
    let mut s = String::from("hello");

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

    // ✅ 不可变引用使用完后,可以创建可变引用
    let r3 = &mut s;
    r3.push_str(" world");
    println!("{}", r3);

    // ❌ 不能同时有不可变引用和可变引用
    // let r4 = &s;
    // let r5 = &mut s;
    // println!("{} {}", r4, r5); // 编译错误
}

非词法生命周期(NLL)

Rust 2018+ 引入 NLL,引用的生命周期在最后一次使用时结束:

fn main() {
    let mut s = String::from("hello");

    let r1 = &s;
    let r2 = &s;
    println!("{} {}", r1, r2);
    // r1 和 r2 在此之后不再使用(NLL),生命周期结束

    let r3 = &mut s; // ✅ 此时可以创建可变引用
    r3.push_str(" world");
    println!("{}", r3);
}

6.6 悬垂引用(Dangling Reference)

Rust 编译器禁止悬垂引用:

// ❌ 编译错误:返回对局部变量的引用
// fn dangle() -> &String {
//     let s = String::from("hello");
//     &s // s 在函数结束时被释放
// }

// ✅ 正确做法:返回所有权
fn no_dangle() -> String {
    let s = String::from("hello");
    s // 移动所有权给调用者
}

fn main() {
    let s = no_dangle();
    println!("{}", s);
}

6.7 生命周期(Lifetime)

为什么需要生命周期

// ❌ 编译器不知道返回值的生命周期与哪个参数相关
// fn longest(x: &str, y: &str) -> &str {
//     if x.len() > y.len() { x } else { y }
// }

// ✅ 使用生命周期标注
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

fn main() {
    let s1 = String::from("long string");
    let result;
    {
        let s2 = String::from("xyz");
        result = longest(s1.as_str(), s2.as_str());
        println!("最长的字符串: {}", result);
    }
    // println!("{}", result); // ❌ 可能编译错误:s2 已释放
}

生命周期语法

语法说明
'a生命周期参数名称(通常用小写字母)
&'a T带生命周期的不可变引用
&'a mut T带生命周期的可变引用
T: 'aT 中的所有引用至少活 'a 那么久

结构体中的生命周期

#[derive(Debug)]
struct Excerpt<'a> {
    text: &'a str,
}

impl<'a> Excerpt<'a> {
    fn level(&self) -> i32 {
        3
    }

    fn announce(&self, announcement: &str) -> &str {
        println!("注意: {}", announcement);
        self.text // 返回的生命周期与 self 相同
    }
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence;
    {
        let i = novel.find('.').unwrap_or(novel.len());
        first_sentence = Excerpt {
            text: &novel[..i],
        };
    }
    println!("摘录: {:?}", first_sentence);
}

生命周期省略规则

编译器会自动推断生命周期,不需要总是手动标注。三条规则:

  1. 每个引用参数获得各自的生命周期
  2. 如果只有一个输入生命周期,该生命周期赋给所有输出引用
  3. 如果方法有 &self&mut selfself 的生命周期赋给所有输出引用
// 不需要标注的情况
fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    for (i, &byte) in bytes.iter().enumerate() {
        if byte == b' ' {
            return &s[..i];
        }
    }
    &s[..]
}

// 等价于标注后
// fn first_word<'a>(s: &'a str) -> &'a str { ... }

fn main() {
    let s = String::from("hello world");
    let word = first_word(&s);
    println!("第一个单词: {}", word);
}

‘static 生命周期

// 'static 生命周期的数据存在于整个程序运行期间
let s: &'static str = "我有静态生命周期";

fn main() {
    // 字符串字面量都是 'static
    let greeting: &'static str = "hello";
    println!("{}", greeting);
}

6.8 同时使用泛型、trait bound 和生命周期

use std::fmt::Display;

fn longest_with_announcement<'a, T>(
    x: &'a str,
    y: &'a str,
    ann: T,
) -> &'a str
where
    T: Display,
{
    println!("公告: {}", ann);
    if x.len() > y.len() { x } else { y }
}

fn main() {
    let s1 = String::from("long string");
    let s2 = "xyz";

    let result = longest_with_announcement(
        s1.as_str(),
        s2,
        "正在比较两个字符串的长度!",
    );
    println!("最长: {}", result);
}

6.9 业务场景示例

缓存系统(所有权视角)

use std::collections::HashMap;

struct Cache {
    data: HashMap<String, String>,
    max_size: usize,
}

impl Cache {
    fn new(max_size: usize) -> Self {
        Self {
            data: HashMap::new(),
            max_size,
        }
    }

    // 获取值的引用(借用)
    fn get(&self, key: &str) -> Option<&String> {
        self.data.get(key)
    }

    // 插入值(转移所有权)
    fn set(&mut self, key: String, value: String) {
        if self.data.len() >= self.max_size {
            // 移除一个旧条目(释放所有权)
            if let Some(oldest_key) = self.data.keys().next().cloned() {
                self.data.remove(&oldest_key);
            }
        }
        self.data.insert(key, value);
    }

    // 获取大小
    fn len(&self) -> usize {
        self.data.len()
    }
}

fn main() {
    let mut cache = Cache::new(3);

    // set 获得 key 和 value 的所有权
    cache.set("user:1".to_string(), "Alice".to_string());
    cache.set("user:2".to_string(), "Bob".to_string());
    cache.set("user:3".to_string(), "Charlie".to_string());

    // get 返回引用(借用)
    if let Some(name) = cache.get("user:1") {
        println!("用户1: {}", name);
    }

    // 添加第4个条目,会驱逐一个旧条目
    cache.set("user:4".to_string(), "David".to_string());
    println!("缓存大小: {}", cache.len()); // 3
}

文本处理器

/// 统计文本中每个单词的出现次数
/// 参数是不可变引用,不会夺走调用者的数据
fn word_frequency(text: &str) -> Vec<(&str, usize)> {
    let mut freq = std::collections::HashMap::new();

    for word in text.split_whitespace() {
        // entry API 返回可变引用
        *freq.entry(word).or_insert(0) += 1;
    }

    let mut result: Vec<_> = freq.into_iter().collect();
    result.sort_by(|a, b| b.1.cmp(&a.1));
    result
}

/// 提取文本摘要(前 n 个句子)
/// 返回的是对原始文本的切片引用
fn summarize(text: &str, n: usize) -> &str {
    let mut count = 0;
    for (i, ch) in text.char_indices() {
        if ch == '.' || ch == '。' {
            count += 1;
            if count == n {
                return &text[..=i];
            }
        }
    }
    text // 不足 n 句则返回全部
}

fn main() {
    let article = "Rust is a systems programming language. \
                   Rust is fast and safe. Rust has ownership. \
                   Rust prevents data races. Rust is loved.";

    // 借用 article,不转移所有权
    println!("摘要: {}", summarize(article, 2));
    println!();

    let freq = word_frequency(article);
    println!("词频统计:");
    for (word, count) in freq.iter().take(5) {
        println!("  \"{}\": {}次", word, count);
    }

    // article 仍然可用
    println!("\n原文长度: {} 字节", article.len());
}

6.10 常见错误与解决方案

错误1:在借用期间修改

fn main() {
    let mut v = vec![1, 2, 3, 4, 5];

    // ❌ 在迭代(借用)期间修改
    // for &item in &v {
    //     if item == 3 {
    //         v.push(6); // 不能在不可变借用期间可变借用
    //     }
    // }

    // ✅ 方案1:收集要修改的信息,迭代后再修改
    let should_add = v.contains(&3);
    if should_add {
        v.push(6);
    }
    println!("{:?}", v);

    // ✅ 方案2:使用 retain 等迭代器方法
    let mut v2 = vec![1, 2, 3, 4, 5, 6];
    v2.retain(|&x| x != 3); // 移除所有等于3的元素
    println!("retain后: {:?}", v2);
}

错误2:返回局部变量的引用

// ❌ 悬垂引用
// fn create_string() -> &str {
//     let s = String::from("hello");
//     &s // s 在函数结束后被释放
// }

// ✅ 返回所有权
fn create_string() -> String {
    String::from("hello")
}

// ✅ 返回 'static 引用
fn create_static() -> &'static str {
    "hello" // 字面量有 'static 生命周期
}

fn main() {
    let s1 = create_string();
    let s2 = create_static();
    println!("{}, {}", s1, s2);
}

错误3:闭包捕获所有权

fn main() {
    let name = String::from("Alice");

    // move 闭包获得所有权
    let closure = move || {
        println!("Hello, {}", name);
    };

    closure();
    // println!("{}", name); // ❌ name 已移动到闭包中

    // 如果是 Copy 类型则无此问题
    let n = 42;
    let closure2 = move || println!("n = {}", n);
    closure2();
    println!("n 仍然有效: {}", n); // ✅ i32 是 Copy 类型
}

6.11 本章小结

要点说明
所有权每个值有且仅有一个所有者,离开作用域自动释放
移动非 Copy 类型赋值时所有权移动,原变量失效
克隆clone() 创建深拷贝,独立副本
不可变引用&T,可有多个,只读访问
可变引用&mut T,同一时间只能有一个
生命周期编译器确保引用始终有效,不出现悬垂引用
NLL引用生命周期在最后一次使用时结束
省略规则编译器可自动推断大部分生命周期

扩展阅读

  1. The Rust Book - 所有权 — 官方教程
  2. The Rust Book - 生命周期 — 生命周期详解
  3. Rustonomicon — 深入 Rust 内部机制
  4. Visualizing memory layout — Rust 内存布局可视化