概述

所有权(Ownership)是 Rust 最独特的特性,它让 Rust 无需垃圾回收器就能保证内存安全。理解所有权对于掌握 Rust 至关重要。

所有权的三条规则

  1. Rust 中的每一个值都有一个被称为其所有者(owner)的变量

  2. 值在任一时刻有且只有一个所有者

  3. 当所有者(变量)离开作用域,这个值将被丢弃

栈与堆

栈(Stack)

  • 后进先出(LIFO)

  • 存储已知大小的数据

  • 快速高效

  • 所有数据必须占用已知且固定的大小

堆(Heap)

  • 用于存储编译时未知大小的数据

  • 分配内存时需要在堆上找一块足够大的空间

  • 通过指针访问堆上的数据

  • 分配和释放比栈慢

fn main() {
    // 栈上分配的值
    let x = 5;
    let y = x;  // x 仍然有效,因为实现了 Copy trait
    
    println!("x = {}, y = {}", x, y);
    
    // 堆上分配的值
    let s1 = String::from("hello");
    let s2 = s1;  // s1 已经失效,所有权转移给 s2
    
    // println!("{}", s1);  // 错误!s1 已经失效
    println!("{}", s2);
}

所有权机制

移动(Move)

fn main() {
    let s1 = String::from("hello");
    let s2 = s1;  // s1 的所有权移动到 s2
    
    // s1 不再有效
    // println!("{}", s1);  // 编译错误
    
    println!("{}", s2);
}

克隆(Clone)

如果需要深度复制,可以使用 clone 方法:

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();  // 深度复制
    
    println!("s1 = {}, s2 = {}", s1, s2);
}

Copy trait

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

fn main() {
    let x = 5;
    let y = x;  // x 仍然有效
    
    println!("x = {}, y = {}", x, y);
    
    // 实现 Copy 的类型:
    // - 所有整数类型
    // - 布尔类型
    // - 浮点数类型
    // - 字符类型
    // - 元组(如果所有元素都实现了 Copy)
}

函数与所有权

传递值给函数

fn main() {
    let s = String::from("hello");  // s 进入作用域
    takes_ownership(s);              // s 的值移动到函数中
    // s 不再有效
    
    let x = 5;                       // x 进入作用域
    makes_copy(x);                   // x 移动到函数中
    println!("x = {}", x);           // x 仍然有效
}

fn takes_ownership(some_string: String) {
    println!("{}", some_string);
}  // some_string 离开作用域,`drop` 被调用,内存被释放

fn makes_copy(some_integer: i32) {
    println!("{}", some_integer);
}  // some_integer 离开作用域,但不需要特殊处理

返回值与作用域

fn main() {
    let s1 = gives_ownership();         // gives_ownership 将返回值移动给 s1
    println!("s1 = {}", s1);
    
    let s2 = String::from("hello");     // s2 进入作用域
    let s3 = takes_and_gives_back(s2);  // s2 移动到函数中,返回值移动给 s3
    println!("s3 = {}", s3);
    // s2 不再有效
}

fn gives_ownership() -> String {
    let some_string = String::from("hello");  // some_string 进入作用域
    some_string                               // some_string 被返回并移动给调用者
}

fn takes_and_gives_back(a_string: String) -> String {
    a_string  // a_string 被返回并移动给调用者
}

引用与借用

不可变引用

fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1);  // 借用 s1
    
    println!("'{}' 的长度是 {}", s1, len);
}

fn calculate_length(s: &String) -> usize {  // s 是对 String 的引用
    s.len()
}  // s 离开作用域,但因为它没有所有权,所以不释放内存

可变引用

fn main() {
    let mut s = String::from("hello");
    change(&mut s);  // 可变借用
    println!("{}", s);
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

引用的规则

  1. 在任意给定时间,要么:

    • 只能有一个可变引用
  2. 或者:

    • 只能有多个不可变引用
  3. 引用必须始终有效

fn main() {
    let mut s = String::from("hello");
    
    let r1 = &s;  // 没问题
    let r2 = &s;  // 没问题
    println!("{} 和 {}", r1, r2);
    // r1 和 r2 不再使用了
    
    let r3 = &mut s;  // 没问题
    println!("{}", r3);
    
    // 以下代码会编译错误:
    // let r1 = &mut s;
    // let r2 = &mut s;  // 错误!不能有多个可变引用
}

悬垂引用

fn main() {
    // let reference_to_nothing = dangle();
}

// 返回引用的函数不能正确工作
// fn dangle() -> &String {  // 错误!返回引用的字符串
//     let s = String::from("hello");  // s 是一个新 String
//     &s  // 返回 s 的引用
// }  // s 离开作用域并被丢弃,其内存被释放
// // 返回的引用指向一个无效的 String

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

切片(Slice)

字符串切片

fn main() {
    let s = String::from("hello world");
    
    let hello = &s[0..5];   // 字符串切片
    let world = &s[6..11];
    
    println!("hello = {}, world = {}", hello, world);
    
    // 从开头开始可以省略 0
    let slice = &s[..5];
    
    // 到结尾可以省略最后一个数字
    let slice = &s[6..];
    
    // 同时省略两个值可以获取整个字符串
    let slice = &s[..];
    
    println!("切片: {}", slice);
}

字符串字面量就是切片

fn main() {
    let s = "Hello, world!";  // s 的类型是 &str
    println!("{}", s);
}

在函数中使用字符串切片

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[..]
}

fn main() {
    let my_string = String::from("hello world");
    
    // first_word 适用于 String 的切片
    let word = first_word(&my_string[0..6]);
    println!("第一个词: {}", word);
    
    let word = first_word(&my_string[..]);
    println!("第一个词: {}", word);
    
    // first_word 也适用于 String 的引用
    let word = first_word(&my_string);
    println!("第一个词: {}", word);
    
    // first_word 也适用于字符串字面量
    let word = first_word("hello world");
    println!("第一个词: {}", word);
}

其他类型的切片

fn main() {
    let a = [1, 2, 3, 4, 5];
    
    let slice = &a[1..3];
    
    println!("数组切片: {:?}", slice);
    assert_eq!(slice, &[2, 3]);
}

实际应用示例

查找第一个单词

fn first_word(s: &String) -> usize {
    let bytes = s.as_bytes();
    
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return i;
        }
    }
    
    s.len()
}

fn main() {
    let mut s = String::from("hello world");
    let word_index = first_word(&s);  // 返回索引
    
    s.clear();  // 清空字符串
    
    // word_index 在这里仍然有效!但字符串已经改变
    // 这可能导致问题
}

改进版本,使用字符串切片:

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[..]
}

fn main() {
    let mut s = String::from("hello world");
    let word = first_word(&s);  // 返回切片
    
    s.clear();  // 编译错误!
    // println!("第一个词是: {}", word);  // 编译器会阻止这行代码
}

总结

本教程详细介绍了 Rust 的所有权与借用机制:

  1. 所有权规则

    • 每个值都有一个所有者
    • 值在任一时刻只有一个所有者
    • 所有者离开作用域时值被丢弃
  2. 移动与克隆

    • 默认情况下,赋值会移动所有权
    • 使用 clone 方法进行深度复制
    • 实现了 Copy trait 的类型会自动复制
  3. 函数与所有权

    • 传递值给函数会移动所有权
    • 返回值可以传递所有权
  4. 引用与借用

    • 引用允许在不获取所有权的情况下使用值
    • 有不可变引用和可变引用
    • 引用的规则保证内存安全
  5. 切片

    • 引用数组或字符串的一部分
    • 字符串切片是 &str
    • 使用切片可以避免所有权问题

所有权是 Rust 最核心的特性,它使 Rust 能够在编译时保证内存安全,而无需垃圾回收器。虽然一开始可能需要一些时间来适应,但一旦理解了所有权,你就能编写出安全、高效的 Rust 代码。

下一步

在下一教程中,我们将学习 Rust 的结构体与枚举,这是 Rust 中定义自定义类型的重要方式。我们将了解:

  • 定义和使用结构体

  • 方法和关联函数

  • 枚举和模式匹配

  • Option 类型

  • match 表达式的深入应用

继续学习 Rust,掌握这门强大语言的更多特性!