🦀 第04课:所有权与借用

这是Rust最核心、最独特、也是最让初学者困惑的特性——所有权系统。理解所有权是掌握Rust的关键,它让Rust无需垃圾回收器就能保证内存安全。

入门基础 第4/25课

学习目标:理解所有权三大规则、掌握移动与克隆、学会借用与引用、理解生命周期基础

🏛️ 所有权三大规则

  1. Rust中每个值都有一个所有者(owner)
  2. 同一时刻,值只能有一个所有者
  3. 当所有者离开作用域,值被丢弃(drop)

作用域与Drop

fn main() {
    {
        let s = String::from("hello");  // s从此时开始有效
        println!("{}", s);               // 使用s
    }  // 作用域结束,s被drop,内存释放
    // println!("{}", s);  // ❌ 错误!s已不存在
    
    // 对比:栈上的简单类型
    let x = 5;       // i32在栈上
    let y = x;       // 复制值(Copy语义)
    println!("x={}, y={}", x, y);  // ✅ 都有效
}
hello x=5, y=5

✅ 验证通过

📦 移动(Move)语义

对于堆上分配的数据(如String),赋值操作会移动所有权,而不是复制数据:

String移动示意图: s1 ──→ [ptr|len|cap] ──→ 堆: "hello" ↓ 赋值 let s2 = s1 s1 (失效) [ptr|len|cap] ──→ 堆: "hello" ↑ s2 ──────────────────────┘ 为什么?避免双重释放(double free)! 两个指针指向同一内存 → 都drop → 释放两次 → 💥
fn main() {
    let s1 = String::from("hello");
    let s2 = s1;  // s1的所有权移动到s2
    
    // println!("{}", s1);  // ❌ 编译错误!s1已失效
    println!("{}", s2);     // ✅ s2有效
    
    // 函数传参也会移动所有权
    let s3 = String::from("world");
    takes_ownership(s3);
    // println!("{}", s3);  // ❌ s3已被移动到函数中
    
    // 函数返回值也转移所有权
    let s4 = gives_ownership();
    println!("s4 = {}", s4);
    
    // 获取所有权再返回
    let s5 = String::from("rust");
    let s6 = takes_and_gives_back(s5);
    // println!("{}", s5);  // ❌ 已移动
    println!("s6 = {}", s6);
}

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

fn gives_ownership() -> String {
    String::from("来自函数的值")
}

fn takes_and_gives_back(s: String) -> String {
    s  // 原样返回
}
hello 获取所有权: world s4 = 来自函数的值 s6 = rust

✅ 验证通过

📋 克隆(Clone)

如果确实需要深拷贝,使用clone()

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();  // 深拷贝!
    
    println!("s1 = {}, s2 = {}", s1, s2);  // ✅ 两个都有效
    
    // clone代价较高——堆上数据被完整复制
    let v1 = vec![1, 2, 3, 4, 5];
    let v2 = v1.clone();
    println!("v1 = {:?}, v2 = {:?}", v1, v2);
}
s1 = hello, s2 = hello v1 = [1, 2, 3, 4, 5], v2 = [1, 2, 3, 4, 5]

✅ 验证通过

📋 Copy特征 —— 栈上类型的自动复制

实现了Copy特征的类型在赋值时会自动复制,不会移动:

fn main() {
    // 这些类型实现了Copy:
    let x: i32 = 42;
    let y = x;          // 复制
    println!("x={}, y={}", x, y);  // ✅ 都有效
    
    let b: bool = true;
    let b2 = b;         // 复制
    println!("b={}, b2={}", b, b2);  // ✅ 都有效
    
    let c: char = '🦀';
    let c2 = c;         // 复制
    println!("c={}, c2={}", c, c2);  // ✅ 都有效
    
    let t: (i32, f64) = (1, 2.0);
    let t2 = t;         // 复制(元素都是Copy的)
    println!("t={:?}, t2={:?}", t, t2);  // ✅ 都有效
    
    let arr: [i32; 3] = [1, 2, 3];
    let arr2 = arr;     // 复制(固定大小且元素是Copy的)
    println!("arr={:?}, arr2={:?}", arr, arr2);  // ✅ 都有效
    
    // ⚠️ 不可Copy的类型:
    // let s = String::from("hello");
    // let s2 = s;  // 移动!String不是Copy
    // println!("{}", s);  // ❌
}
x=42, y=42 b=true, b2=true c=🦀, c2=🦀 t=(1, 2.0), t2=(1, 2.0) arr=[1, 2, 3], arr2=[1, 2, 3]

✅ 验证通过

💡 Copy规则:如果一个类型的所有组件都是Copy的,且没有实现Drop,它就可以是Copy的。一般规则:简单标量类型是Copy,需要堆分配或持有资源的类型不是Copy。

🔗 引用与借用

每次都移动所有权太麻烦了!Rust提供了引用机制——借用值而不获取所有权。

不可变引用 &T

fn calculate_length(s: &String) -> usize {  // 借用String
    s.len()
}  // s离开作用域,但因为它不拥有所有权,不会drop

fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1);  // &创建引用
    // s1仍然有效!
    println!("'{}'的长度是{}", s1, len);
    
    // 同时存在多个不可变引用 ✅
    let r1 = &s1;
    let r2 = &s1;
    println!("r1={}, r2={}", r1, r2);
}
'hello'的长度是5 r1=hello, r2=hello

✅ 验证通过

可变引用 &mut T

fn append_suffix(s: &mut String) {
    s.push_str(", Rust!");
}

fn main() {
    let mut s = String::from("Hello");
    append_suffix(&mut s);
    println!("{}", s);  // "Hello, Rust!"
    
    // ⚠️ 同一时刻只能有一个可变引用
    let r1 = &mut s;
    // let r2 = &mut s;  // ❌ 编译错误!不能同时有两个&mut
    
    r1.push_str(" 🦀");
    println!("{}", r1);
    
    // r1离开作用域后可以创建新的可变引用
    let r2 = &mut s;
    r2.push_str("!!");
    println!("{}", r2);
}
Hello, Rust! Hello, Rust! 🦀 Hello, Rust! 🦀!!

✅ 验证通过

🔑 引用规则

规则1:同一时刻,你可以拥有任意数量的不可变引用(&T),或者恰好一个可变引用(&mut T)。

规则2:引用必须始终有效(不能有悬垂引用)。

引用安全保证: 多个 &T ──→ 读取 ──→ ✅ 安全(多人同时读) 一个 &mut T ──→ 修改 ──→ ✅ 安全(独占修改权) &T + &mut T ──→ 读写 ──→ ❌ 数据竞争! 多个 &mut T ──→ 多写 ──→ ❌ 数据竞争! Rust在编译期就阻止了这些!

NLL(Non-Lexical Lifetimes)

fn main() {
    let mut s = String::from("hello");
    
    let r1 = &s;       // 不可变引用
    let r2 = &s;       // 不可变引用
    println!("r1={}, r2={}", r1, r2);
    // r1和r2在此之后不再使用(NLL)
    
    let r3 = &mut s;   // ✅ 允许!因为r1/r2已不再使用
    r3.push_str(" world");
    println!("r3={}", r3);
}
r1=hello, r2=hello r3=hello world

✅ 验证通过

悬垂引用 —— Rust杜绝!

// ❌ 编译错误!Rust阻止了悬垂引用
// fn dangle() -> &String {
//     let s = String::from("hello");
//     &s  // s在函数结束时被drop,返回的引用指向已释放的内存
// }

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

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

✅ 验证通过

🏗️ 所有权实战:构建简单缓冲区

struct Buffer {
    data: Vec<u8>,
}

impl Buffer {
    fn new(capacity: usize) -> Self {
        Buffer {
            data: Vec::with_capacity(capacity),
        }
    }
    
    fn write(&mut self, bytes: &[u8]) {  // 借用slice,不需要所有权
        self.data.extend_from_slice(bytes);
    }
    
    fn read(&self) -> &[u8] {  // 返回不可变引用
        &self.data
    }
    
    fn len(&self) -> usize {
        self.data.len()
    }
    
    fn clear(&mut self) {
        self.data.clear();
    }
    
    fn into_vec(self) -> Vec<u8> {  // 消耗self,返回所有权
        self.data
    }
}

fn main() {
    let mut buf = Buffer::new(100);
    buf.write(b"Hello");
    buf.write(b" ");
    buf.write(b"Rust!");
    
    println!("缓冲区内容: {}", String::from_utf8_lossy(buf.read()));
    println!("长度: {}", buf.len());
    
    // 读取不影响所有权
    let data = buf.read();
    println!("数据: {:?}", data);
    println!("长度: {}", buf.len());  // buf仍然可用
    
    // 消耗buffer获取内部数据
    let vec = buf.into_vec();
    // buf.len();  // ❌ buf已被消耗
    println!("提取的Vec: {:?}", vec);
}
缓冲区内容: Hello Rust! 长度: 11 数据: [72, 101, 108, 108, 111, 32, 82, 117, 115, 116, 33] 长度: 11 提取的Vec: [72, 101, 108, 108, 111, 32, 82, 117, 115, 116, 33]

✅ 验证通过

📝 练习

练习1:修复编译错误

fn main() {
    let s1 = String::from("hello");
    let s2 = s1;
    println!("{}", s1);  // 修复这行
}

练习2:字符串反转

编写函数reverse(s: &str) -> String,接受字符串引用,返回反转后的新字符串。不修改原始字符串。

练习3:统计单词

编写函数count_words(text: &str) -> usize,统计字符串中的单词数。

练习4:可变统计

编写结构体Stats,包含字段count和total。实现方法add(&mut self, value: f64)average(&self) -> f64

🏆 本课成就

🔒 下一课解锁:字符串与切片 —— 深入理解Rust的字符串类型

🔄 所有权与并发安全

use std::thread;
use std::sync::{Arc, Mutex};

// 所有权系统如何保证并发安全
fn main() {
    // ❌ 编译器阻止不安全的并发
    // let mut data = vec![1, 2, 3];
    // thread::spawn(|| { data.push(4); });  // ❌ data可能被多个线程访问
    
    // ✅ 使用Arc+Mutex安全共享
    let data = Arc::new(Mutex::new(vec![1, 2, 3]));
    
    let mut handles = vec![];
    for i in 0..3 {
        let data = Arc::clone(&data);
        handles.push(thread::spawn(move || {
            let mut d = data.lock().unwrap();
            d.push(i + 10);
        }));
    }
    for h in handles { h.join().unwrap(); }
    println!("并发结果: {:?}", data.lock().unwrap());
    
    // ✅ 使用通道通信
    use std::sync::mpsc;
    let (tx, rx) = mpsc::channel();
    for i in 0..5 {
        let tx = tx.clone();
        thread::spawn(move || { tx.send(i).unwrap(); });
    }
    drop(tx);  // 关闭原始发送端
    let received: Vec<_> = rx.iter().collect();
    println!("通道接收: {:?}", received);
}
并发结果: [1, 2, 3, 10, 11, 12] 通道接收: [0, 1, 2, 3, 4]

✅ 验证通过

📊 所有权模式速查

场景方案示例
函数只读数据&Tfn print(s: &str)
函数修改数据&mut Tfn push(v: &mut Vec)
函数消费数据Tfn process(s: String)
返回新数据Tfn create() -> String
返回借用数据&T + 生命周期fn get(&self) -> &str
多所有者Rc<T>/Arc<T>Rc::clone(&data)
内部可变RefCell<T>cell.borrow_mut()
避免引用Owned + Clone使用String而非&str