Rust 练习册 12:所有权系统

所有权系统是 Rust 最核心和最具特色的特性之一,它在不使用垃圾回收器的情况下保证了内存安全。通过所有权系统,Rust 能够在编译时防止悬垂指针、数据竞争和其他内存安全问题。今天我们就来深入学习 Rust 的所有权系统。

什么是所有权?

在 Rust 中,每个值都有一个所有者(owner),并且在任何时刻都只有一个所有者。当所有者离开作用域时,该值将被丢弃。这个简单的规则构成了 Rust 所有权系统的基础。

项目中的示例代码

让我们先看看项目中的示例代码:

rust 复制代码
#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        let answer = "42";
        let no_answer = answer;
        assert_eq!(answer, no_answer);
    }
}

在这个示例中,我们做了以下几件事:

  1. 使用 let answer = "42" 声明了一个变量 answer,它拥有字符串字面量 "42" 的所有权
  2. 使用 let no_answer = answeranswer 的值移动(move)给 no_answer
  3. 比较两个变量的值,断言它们相等

对于字符串字面量(&str),这种移动实际上是复制了引用,因为 &str 实现了 Copy trait。但对于没有实现 Copy trait 的类型,这种赋值会导致所有权的转移。

所有权规则

Rust 的所有权系统基于以下三条规则:

  1. Rust 中的每个值都有一个所有者
  2. 任何时刻都只有一个所有者
  3. 当所有者离开作用域时,该值将被丢弃

变量作用域

rust 复制代码
fn variable_scope() {
    {
        let s = "hello"; // s 进入作用域
        println!("{}", s);
    } // s 离开作用域,值被丢弃
    // println!("{}", s); // 这会编译错误!
}

移动(Move)语义

基本移动

rust 复制代码
fn move_semantics() {
    let s1 = String::from("hello");
    let s2 = s1; // s1 的所有权移动到 s2
    
    // println!("{}", s1); // 这会编译错误!
    println!("{}", s2); // 这是正确的
}

函数参数中的移动

rust 复制代码
fn give_ownership() -> String {
    let some_string = String::from("hello");
    some_string // 返回值移动给调用者
}

fn take_ownership(some_string: String) {
    println!("{}", some_string);
} // some_string 离开作用域并被丢弃

fn move_example() {
    let s1 = give_ownership();
    take_ownership(s1); // s1 的所有权移动到函数参数
    // s1 不再有效
}

克隆(Clone)语义

深拷贝

rust 复制代码
fn clone_semantics() {
    let s1 = String::from("hello");
    let s2 = s1.clone(); // 创建 s1 的深拷贝
    
    println!("s1 = {}, s2 = {}", s1, s2); // 两个变量都有效
}

Copy trait

实现 Copy 的类型

rust 复制代码
fn copy_trait() {
    let x = 5;
    let y = x; // 对于实现 Copy 的类型,这里是复制而不是移动
    
    println!("x = {}, y = {}", x, y); // x 和 y 都有效
}

fn copy_types() {
    // 这些类型都实现了 Copy trait
    let a: i32 = 5;
    let b: bool = true;
    let c: char = 'a';
    let d: f64 = 3.14;
    let e: (i32, f64) = (1, 2.0);
    
    let a_copy = a;
    let b_copy = b;
    let c_copy = c;
    let d_copy = d;
    let e_copy = e;
    
    // 原始变量仍然有效
    println!("{}, {}, {}, {}, {:?}", a, b, c, d, e);
    println!("{}, {}, {}, {}, {:?}", a_copy, b_copy, c_copy, d_copy, e_copy);
}

所有权与函数

将值传递给函数时也会发生所有权转移或复制:

rust 复制代码
fn take_ownership(s: String) {
    println!("{}", s);
} // s 在这里超出作用域并被删除

fn make_copy(i: i32) {
    println!("{}", i);
} // i 超出作用域,但由于是 Copy 类型,不需要特殊处理

fn function_ownership() {
    let s = String::from("hello");
    take_ownership(s); // s 的所有权被移动到函数中
    // println!("{}", s); // 这会编译错误!
    
    let x = 5;
    make_copy(x); // x 被复制
    println!("{}", x); // x 仍然可以使用
}

引用与借用

不可变引用

rust 复制代码
fn immutable_borrowing() {
    let s = String::from("hello");
    let len = calculate_length(&s); // 传递引用,不转移所有权
    println!("The length of '{}' is {}.", s, len);
}

fn calculate_length(s: &String) -> usize {
    s.len() // 不获取所有权
} // s 离开作用域,但不丢弃它指向的数据

可变引用

rust 复制代码
fn mutable_borrowing() {
    let mut s = String::from("hello");
    change(&mut s); // 传递可变引用
    println!("{}", s);
}

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

借用规则

rust 复制代码
fn borrowing_rules() {
    let mut s = String::from("hello");
    
    let r1 = &s; // 第一个不可变引用
    let r2 = &s; // 第二个不可变引用
    // let r3 = &mut s; // 这会编译错误!不能同时拥有不可变和可变引用
    
    println!("{} and {}", r1, r2);
    
    // 在最后一个不可变引用使用后,可以创建可变引用
    let r3 = &mut s;
    println!("{}", r3);
}

实际应用示例

字符串处理

rust 复制代码
fn string_processing() {
    let s1 = String::from("hello");
    let s2 = String::from("world");
    
    let combined = concatenate(s1, s2); // s1 和 s2 的所有权被移动
    println!("Combined string: {}", combined);
}

fn concatenate(s1: String, s2: String) -> String {
    let mut result = s1; // 获取 s1 的所有权
    result.push(' ');
    result.push_str(&s2); // 借用 s2
    result
}

Vector 处理

rust 复制代码
fn vector_processing() {
    let vec1 = vec![1, 2, 3];
    let vec2 = vec![4, 5, 6];
    
    let merged = merge_vectors(vec1, vec2); // 所有权被移动
    println!("Merged vector: {:?}", merged);
}

fn merge_vectors(v1: Vec<i32>, v2: Vec<i32>) -> Vec<i32> {
    let mut result = v1; // 获取 v1 的所有权
    result.extend(v2); // 获取 v2 的所有权并扩展
    result
}

引用计数智能指针

Rc(引用计数)

rust 复制代码
use std::rc::Rc;

fn reference_counting() {
    let data = Rc::new(String::from("data"));
    println!("Reference count: {}", Rc::strong_count(&data));
    
    let data1 = Rc::clone(&data);
    println!("Reference count: {}", Rc::strong_count(&data));
    
    let data2 = Rc::clone(&data);
    println!("Reference count: {}", Rc::strong_count(&data));
    
    drop(data1);
    println!("Reference count: {}", Rc::strong_count(&data));
}

最佳实践

1. 合理使用引用

rust 复制代码
// 好的做法:使用引用避免不必要的所有权转移
fn good_borrowing(s: &String) -> usize {
    s.len()
}

// 避免:不必要的所有权获取
fn avoid_taking_ownership(s: String) -> usize {
    s.len()
    // s 在函数结束时被丢弃
}

2. 选择合适的参数类型

rust 复制代码
// 对于只读操作,使用 &str 而不是 &String
fn better_string_function(s: &str) -> usize {
    s.len()
}

fn string_parameter_example() {
    let string = String::from("hello");
    let str_slice = "world";
    
    // 两种类型都可以传递给函数
    println!("{}", better_string_function(&string));
    println!("{}", better_string_function(str_slice));
}

与项目代码的深入分析

让我们回到原始项目代码,深入分析其中的所有权概念:

rust 复制代码
#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        let answer = "42";
        let no_answer = answer;
        assert_eq!(answer, no_answer);
    }
}

这段代码展示了:

  1. 字符串字面量的所有权let answer = "42" 创建了一个 &str 类型的变量
  2. Copy trait 的应用let no_answer = answer 实际上是复制而不是移动,因为 &str 实现了 Copy trait
  3. 值的比较:两个变量都可以使用,因为它们各自拥有相同的值

如果我们使用 String 类型,情况会有所不同:

rust 复制代码
fn string_ownership_example() {
    let answer = String::from("42");
    let no_answer = answer; // 所有权从 answer 移动到 no_answer
    // println!("{}", answer); // 这会编译错误!
    println!("{}", no_answer);
}

常见错误和解决方案

使用已移动的值

rust 复制代码
fn use_after_move() {
    // 错误示例
    // let s1 = String::from("hello");
    // let s2 = s1;
    // println!("{}", s1); // 编译错误:s1 的值已被移动
    
    // 正确做法
    let s1 = String::from("hello");
    let s2 = s1.clone(); // 创建副本
    println!("s1: {}, s2: {}", s1, s2);
}

悬垂引用

rust 复制代码
fn dangling_references() {
    // 错误示例
    // fn dangle() -> &String {
    //     let s = String::from("hello");
    //     &s // 返回对将要被丢弃的值的引用
    // } // s 离开作用域并被丢弃
    
    // 正确做法
    fn no_dangle() -> String {
        let s = String::from("hello");
        s // 返回所有权
    }
    
    let s = no_dangle();
    println!("{}", s);
}

总结

Rust 的所有权系统是其内存安全保证的核心:

  1. 每个值都有一个所有者
  2. 任何时刻都只有一个所有者
  3. 当所有者离开作用域时,值将被丢弃

关键要点:

  • 移动语义避免了深拷贝的性能损失
  • Copy trait 使某些类型在赋值时自动复制
  • 引用(借用)允许使用值而不获取所有权
  • 借用检查器在编译时防止内存安全问题

通过合理使用这些特性,我们可以编写出既安全又高效的 Rust 代码,避免了传统系统编程语言中常见的内存安全问题。

相关推荐
逻极2 小时前
Rust数据类型(下):复合类型详解
开发语言·后端·rust
间彧2 小时前
AOP中的五种通知类型在实际项目中如何选择?举例说明各自的典型应用场景
后端
间彧2 小时前
Spring Boot中很多Advice后缀的注解和类,都是干什么的
后端
星释2 小时前
Rust 练习册 16:Trait 作为返回类型
java·网络·rust
2301_796512522 小时前
Rust编程学习 - 如何理解Rust 语言提供了所有权、默认move 语义、借用、生命周期、内部可变性
java·学习·rust
tianyuanwo2 小时前
Rust开发完全指南:从入门到与Python高效融合
开发语言·python·rust
披着羊皮不是狼2 小时前
Spring Boot——从零开始写一个接口:项目构建 + 分层实战
java·spring boot·后端·分层
民乐团扒谱机3 小时前
脉冲在克尔效应下的频谱展宽仿真:原理与 MATLAB 实现
开发语言·matlab·光电·非线性光学·克尔效应
yuan199973 小时前
基于扩展卡尔曼滤波的电池荷电状态估算的MATLAB实现
开发语言·matlab