【Rust编程:从小白入坑】Rust所有权系统

引言

所有权(Ownership)是Rust最独特和最重要的特性,它使Rust能够在没有垃圾回收器的情况下保证内存安全。今天我们将深入学习Rust的所有权系统。

一、什么是所有权?

1.1 所有权规则

Rust的所有权系统有三条核心规则:

  1. Rust中的每个值都有一个被称为其**所有者(owner)**的变量
  2. 值在任何时刻只能有一个所有者
  3. 当所有者离开作用域时,值将被丢弃(dropped)

1.2 作用域

rust 复制代码
fn main() {
    {                      // s不可用,尚未声明
        let s = "hello";   // 从此处开始,s可用
        println!("{}", s); // 使用s
    }                      // s的作用域结束,s不再可用
    
    // println!("{}", s);  // ❌ 错误:s已离开作用域
}

二、String类型与所有权

2.1 String vs 字符串字面量

rust 复制代码
fn main() {
    // 字符串字面量:不可变,固定大小
    let s1 = "hello";
    
    // String类型:可变,大小可变
    let mut s2 = String::from("hello");
    s2.push_str(", world!");
    println!("{}", s2);
}

2.2 内存分配

rust 复制代码
fn main() {
    let s = String::from("hello");
    // s在堆上分配内存
    // 当s离开作用域时,Rust自动调用drop函数释放内存
} // s在这里被drop

三、移动(Move)

3.1 基本类型的复制

rust 复制代码
fn main() {
    let x = 5;
    let y = x;  // 复制
    
    println!("x = {}, y = {}", x, y);  // ✅ 都可用
}

3.2 String的移动

rust 复制代码
fn main() {
    let s1 = String::from("hello");
    let s2 = s1;  // 移动!s1不再有效
    
    // println!("{}", s1);  // ❌ 错误:s1已被移动
    println!("{}", s2);     // ✅ s2可用
}

为什么?

  • String由三部分组成:指针、长度、容量
  • 赋值时只复制这三个值(栈上数据)
  • 为避免二次释放,s1变为无效

3.3 移动语义

rust 复制代码
fn main() {
    let s1 = String::from("hello");
    
    let s2 = s1;  // s1的所有权移动到s2
    
    // s1不再有效
    // let s3 = s1;  // ❌ 错误:s1已被移动
    
    let s3 = s2;  // s2的所有权移动到s3
    println!("{}", s3);
}

四、克隆(Clone)

4.1 深拷贝

如果确实需要深拷贝堆上的数据:

rust 复制代码
fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();  // 深拷贝
    
    println!("s1 = {}, s2 = {}", s1, s2);  // ✅ 都可用
}

注意:clone操作可能很耗时,因为它会复制堆上的数据。

4.2 Copy trait

某些类型实现了Copy trait,赋值时会自动复制:

rust 复制代码
fn main() {
    // 这些类型都实现了Copy
    let x = 5;           // i32
    let y = x;
    
    let a = true;        // bool
    let b = a;
    
    let c = 'z';         // char
    let d = c;
    
    let e = (1, 2);      // 元组(所有元素都是Copy)
    let f = e;
    
    // 都可用
    println!("x={}, y={}", x, y);
    println!("a={}, b={}", a, b);
}

实现Copy的类型

  • 所有整数类型
  • 布尔类型
  • 所有浮点类型
  • 字符类型
  • 元组(仅当其所有字段都是Copy)

五、所有权与函数

5.1 传递参数

rust 复制代码
fn main() {
    let s = String::from("hello");
    
    takes_ownership(s);  // s的所有权移动到函数
    
    // println!("{}", s);  // ❌ 错误:s已被移动
    
    let x = 5;
    makes_copy(x);  // x是Copy类型,会复制
    
    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);
}

5.2 返回值

rust 复制代码
fn main() {
    let s1 = gives_ownership();  // 函数返回值的所有权转移给s1
    println!("{}", s1);
    
    let s2 = String::from("hello");
    let s3 = takes_and_gives_back(s2);  // s2移入,返回值移给s3
    
    // println!("{}", s2);  // ❌ s2已被移动
    println!("{}", s3);     // ✅ s3可用
}

fn gives_ownership() -> String {
    let some_string = String::from("yours");
    some_string  // 返回,所有权移出
}

fn takes_and_gives_back(a_string: String) -> String {
    a_string  // 返回,所有权移出
}

5.3 返回多个值

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)  // 返回String和长度
}

六、实战示例

示例1:交换变量

rust 复制代码
fn main() {
    let s1 = String::from("hello");
    let s2 = String::from("world");
    
    println!("交换前: s1={}, s2={}", s1, s2);
    
    let (s1, s2) = swap(s1, s2);
    
    println!("交换后: s1={}, s2={}", s1, s2);
}

fn swap(a: String, b: String) -> (String, String) {
    (b, a)
}

示例2:字符串处理

rust 复制代码
fn main() {
    let s = String::from("hello");
    let s = add_exclamation(s);
    println!("{}", s);
}

fn add_exclamation(mut s: String) -> String {
    s.push('!');
    s  // 返回所有权
}

示例3:构建器模式

rust 复制代码
struct Config {
    host: String,
    port: u16,
}

impl Config {
    fn new() -> Self {
        Config {
            host: String::from("localhost"),
            port: 8080,
        }
    }
    
    fn host(mut self, host: String) -> Self {
        self.host = host;
        self  // 返回self,允许链式调用
    }
    
    fn port(mut self, port: u16) -> Self {
        self.port = port;
        self
    }
}

fn main() {
    let config = Config::new()
        .host(String::from("127.0.0.1"))
        .port(3000);
    
    println!("{}:{}", config.host, config.port);
}

七、所有权的好处

7.1 内存安全

rust 复制代码
fn main() {
    let s = String::from("hello");
    
    // Rust在编译时防止以下问题:
    // 1. 空指针
    // 2. 悬垂指针
    // 3. 双重释放
    // 4. 内存泄漏
}

7.2 无需垃圾回收

rust 复制代码
fn main() {
    let s = String::from("hello");
    // 编译时就知道何时释放内存
    // 无需运行时垃圾回收
    // 性能可预测
}

八、常见模式

8.1 临时所有权转移

rust 复制代码
fn main() {
    let s = String::from("hello");
    let len = calculate_length_ownership(s);
    
    // ❌ s已不可用
    // println!("{}", s);
}

fn calculate_length_ownership(s: String) -> usize {
    s.len()
}  // s被drop

8.2 使用引用(预告)

rust 复制代码
fn main() {
    let s = String::from("hello");
    let len = calculate_length_ref(&s);  // 传递引用
    
    println!("字符串'{}'的长度是{}", s, len);  // ✅ s仍可用
}

fn calculate_length_ref(s: &String) -> usize {
    s.len()
}  // s(引用)离开作用域,但不会drop原值

九、练习题

练习1:修复所有权错误

rust 复制代码
fn main() {
    let s1 = String::from("hello");
    let s2 = s1;  // s1移动到s2
    
    // 方案1:使用s2
    println!("{}", s2);
    
    // 方案2:克隆s1
    let s1 = String::from("hello");
    let s2 = s1.clone();
    println!("{} {}", s1, s2);
    
    // 方案3:使用引用(下一篇)
}

练习2:函数所有权

rust 复制代码
fn main() {
    let s = String::from("hello");
    process(s);
    
    // 如果后续还需要使用s:
    // 方案1:返回所有权
    let s = String::from("hello");
    let s = process_and_return(s);
    println!("{}", s);
    
    // 方案2:使用引用(下一篇)
}

fn process(s: String) {
    println!("{}", s);
}

fn process_and_return(s: String) -> String {
    println!("{}", s);
    s
}

练习3:字符串拼接

rust 复制代码
fn main() {
    let s1 = String::from("Hello, ");
    let s2 = String::from("world!");
    
    // 方式1:s1会被移动
    let s3 = s1 + &s2;
    println!("{}", s3);
    // println!("{}", s1);  // ❌ s1已移动
    println!("{}", s2);     // ✅ s2可用
    
    // 方式2:使用format!(不移动)
    let s1 = String::from("Hello, ");
    let s2 = String::from("world!");
    let s3 = format!("{}{}", s1, s2);
    println!("{} {} {}", s1, s2, s3);  // 都可用
}

十、所有权流程图

复制代码
创建值
  ↓
值拥有所有者
  ↓
┌───────────────┐
│ 所有权操作?  │
└───────────────┘
  ↓         ↓
移动       克隆
  ↓         ↓
新所有者   两个所有者
  ↓         ↓
离开作用域  各自离开作用域
  ↓         ↓
值被drop    各自被drop

十一、与其他语言对比

C++

cpp 复制代码
// C++:需要手动管理内存
std::string* s = new std::string("hello");
delete s;  // 必须手动释放

Java

java 复制代码
// Java:垃圾回收器自动管理
String s = new String("hello");
// 不确定何时回收,有性能开销

Rust

rust 复制代码
// Rust:编译时确定,零开销
let s = String::from("hello");
// 离开作用域自动释放,无运行时开销

十二、常见错误

错误1:使用已移动的值

rust 复制代码
fn main() {
    let s1 = String::from("hello");
    let s2 = s1;
    
    // ❌ 错误
    // println!("{}", s1);
    
    // ✅ 正确
    println!("{}", s2);
}

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

rust 复制代码
// ❌ 错误:返回悬垂引用
// fn dangle() -> &String {
//     let s = String::from("hello");
//     &s
// }  // s被drop,返回的引用无效

// ✅ 正确:返回所有权
fn no_dangle() -> String {
    let s = String::from("hello");
    s
}

总结

本文学习了:

✅ 所有权的三条规则

✅ 移动(Move)语义

✅ 克隆(Clone)操作

✅ Copy trait

✅ 所有权与函数

核心要点

  1. 每个值有且仅有一个所有者
  2. 赋值或传参会移动所有权
  3. Copy类型会复制,不移动
  4. 离开作用域时自动释放内存
相关推荐
盒马盒马6 小时前
Rust:借用 & 切片
rust
疏狂难除6 小时前
【Tauri2】050——加载html和rust爬虫
开发语言·爬虫·rust·spiderdemo
兰雪簪轩6 小时前
所有权与解构:一次把“拆”与“留”写进类型系统的旅程 ——从语法糖到零拷贝 AST
rust
字节逆旅6 小时前
介绍一个小工具-pake
rust
Zhangzy@7 小时前
仓颉的空安全基石:Option类型的设计与实践
java·开发语言·安全
oioihoii7 小时前
Rust中WebSocket支持的实现
开发语言·websocket·rust
陪我一起学编程8 小时前
Rust 不可变借用:从规则约束到内存安全的深度思考
后端·rust·编程语言
明道源码9 小时前
Kotlin Multiplatform 跨平台方案解析以及热门框架对比
开发语言·kotlin·cocoa
fie88899 小时前
C#实现连续语音转文字
开发语言·c#