7·所有权系统入门

借用与引用

借用与引用

学习目标

  1. 理解不可变引用 &T
  2. 理解可变引用 &mut T
  3. 掌握借用规则
  4. 理解为什么这些规则能防止数据竞争

核心概念

不可变引用(&T)

借用而不获取所有权:

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

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

内存布局

s (栈)           堆内存
┌──────────┐    ┌──────────┐
│ ptr ────────→ │ "hello"  │
│ len: 5   │    └──────────┘
│ cap: 5   │
└──────────┘
     ↑
&s (引用) ──── 指向 s,不拥有堆内存

可变引用(&mut T)

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

fn main() {
    let mut s = String::from("hello");
    push_world(&mut s);
    println!("{}", s);  // "hello, world!"
}

借用规则(核心)

规则 1:任意时刻,只能有以下两种情况之一:
   - 任意数量的不可变引用(&T)
   - 或者恰好一个可变引用(&mut T)
   - 不能同时存在

规则 2:引用必须始终有效(不能悬垂引用)
fn main() {
    let mut s = String::from("hello");

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

    // ✅ 不可变引用用完后,可以创建可变引用
    // (r1, r2 的最后一次使用在上面那行 println!)
    let r3 = &mut s;
    r3.push_str("!");
    println!("{}", r3);
}
fn main() {
    let mut s = String::from("hello");

    let r1 = &s;
    let r2 = &mut s;  // ❌ 不能同时有不可变和可变引用
    println!("{}, {}", r1, r2);
}

为什么有这个规则?

防止数据竞争(data race):

// 假设没有借用规则,这段代码会导致问题:
let mut data = vec![1, 2, 3];
let first = &data[0];      // 不可变引用
data.push(4);               // 可变操作:可能重新分配内存
println!("{}", first);      // first 可能指向已释放的内存!

NLL(Non-Lexical Lifetimes)

引用的生命周期在最后一次使用时结束,而不是作用域结束:

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

    let r1 = &s;           // 不可变引用开始
    let r2 = &s;           // 不可变引用开始
    println!("{} {}", r1, r2);  // r1, r2 最后一次使用 → 生命周期结束

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

切片引用

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    for (i, &byte) in bytes.iter().enumerate() {
        if byte == b' ' {
            return &s[0..i];
        }
    }
    &s[..]
}

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

实践练习

练习 1:修复借用错误

// ❌ 这段代码有编译错误,修复它
fn main() {
    let mut s = String::from("hello");
    let r1 = &s;
    let r2 = &s;
    let r3 = &mut s;
    println!("{}, {}, {}", r1, r2, r3);
}

修复方案

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);
}

练习 2:用借用写函数

// 不获取所有权,计算字符串中某个字符的出现次数
fn count_char(s: &str, target: char) -> usize {
    s.chars().filter(|&c| c == target).count()
}

fn main() {
    let text = String::from("hello world");
    let count = count_char(&text, 'l');
    println!("'l' 出现了 {} 次", count);
    println!("原始文本: {}", text);  // text 仍然有效
}

练习 3:可变借用

fn add_exclamation(s: &mut String) {
    s.push('!');
}

fn main() {
    let mut greeting = String::from("hello");
    add_exclamation(&mut greeting);
    add_exclamation(&mut greeting);
    println!("{}", greeting);  // "hello!!"
}

常见错误

1. 同时持有可变和不可变引用

let mut s = String::from("hello");
let r1 = &s;
s.push_str("!");        // ❌ 不能在 r1 活跃时修改 s
println!("{}", r1);

2. 从函数返回局部变量的引用

// ❌ 悬垂引用
fn dangle() -> &String {
    let s = String::from("hello");
    &s  // s 在函数结束时被 drop,引用指向无效内存
}

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

3. 在持有引用时尝试 move

let mut s = String::from("hello");
let r = &s;
let s2 = s;    // ❌ 不能在借用期间移动
println!("{}", r);

小结

符号含义
&T不可变引用(只读借用)
&mut T可变引用(读写借用)
&s创建不可变引用
&mut s创建可变引用
规则说明
多个 &T可以同时存在
&T + &mut T不能同时存在
&mut T + &mut T不能同时存在
引用必须有效不能悬垂

练习编辑器

rust
Loading...