Skip to content

borrowing.rs

文件信息

  • 📄 原文件:02_borrowing.rs
  • 🔤 语言:rust

完整代码

rust
// ============================================================
//                      借用与引用(Borrowing & References)
// ============================================================
// 借用是 Rust 中不转移所有权就能使用数据的机制
// 引用(&T)就像指针,但保证有效(不会悬垂)
//
// 借用规则(Rust 编译器强制执行):
// 1. 在任意时刻,要么只能有一个可变引用(&mut T),
//    要么可以有多个不可变引用(&T)
// 2. 引用必须始终有效(不能悬垂)
//
// 【记忆口诀】"可以有很多读者,或者一个写者,但不能同时"

fn main() {
    println!("=== 借用与引用 ===");

    // ----------------------------------------------------------
    // 1. 不可变引用(&T)
    // ----------------------------------------------------------
    // 使用 & 创建引用,不获取所有权
    // 可以同时有多个不可变引用

    let s1 = String::from("hello");
    let len = calculate_length(&s1);  // 借用 s1,不移动
    println!("'{}' 的长度是 {}", s1, len);  // s1 仍然有效

    // 多个不可变引用 — OK
    let r1 = &s1;
    let r2 = &s1;
    println!("多个不可变引用: {}, {}", r1, r2);

    // ----------------------------------------------------------
    // 2. 可变引用(&mut T)
    // ----------------------------------------------------------
    // 使用 &mut 创建可变引用,可以修改借用的数据
    // 【限制】同一时刻只能有一个可变引用

    let mut s2 = String::from("hello");
    change(&mut s2);
    println!("修改后: {}", s2);

    // 同一作用域中只能有一个可变引用
    let mut s = String::from("hello");
    let r1 = &mut s;
    // let r2 = &mut s;  // 错误!不能同时有两个可变引用
    r1.push_str(" world");
    println!("可变引用: {}", r1);

    // 【原因】防止数据竞争(data race)
    // 数据竞争发生在以下三个条件同时满足时:
    // 1. 两个或更多指针同时访问同一数据
    // 2. 至少一个指针用于写入
    // 3. 没有同步机制

    // ----------------------------------------------------------
    // 3. 不可变引用和可变引用不能共存
    // ----------------------------------------------------------
    let mut s = String::from("hello");

    let r1 = &s;     // OK:不可变引用
    let r2 = &s;     // OK:可以有多个不可变引用
    println!("不可变引用: {}, {}", r1, r2);
    // r1 和 r2 在这之后不再使用(NLL: Non-Lexical Lifetimes)

    let r3 = &mut s;  // OK:因为 r1, r2 已经不再使用
    r3.push_str(" world");
    println!("可变引用: {}", r3);

    // 【NLL (Non-Lexical Lifetimes)】
    // Rust 2018 之后,引用的生命周期在最后一次使用时结束(不是作用域结束)
    // 这让借用规则更灵活,上面的代码因此可以编译

    // ----------------------------------------------------------
    // 4. 悬垂引用(Dangling Reference)
    // ----------------------------------------------------------
    // Rust 编译器保证引用永远不会悬垂
    // 如果你有一个引用,编译器确保数据在引用之前不会离开作用域

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

    // ----------------------------------------------------------
    // 5. 切片引用(Slices)
    // ----------------------------------------------------------
    // 切片是对集合的部分引用,没有所有权
    // 【类型】&str 是字符串切片,&[T] 是数组切片

    // 字符串切片
    let s = String::from("hello world");
    let hello = &s[0..5];   // 或 &s[..5]
    let world = &s[6..11];  // 或 &s[6..]
    let whole = &s[..];     // 整个字符串
    println!("切片: '{}' '{}' '{}'", hello, world, whole);

    // 【注意】字符串切片的索引是字节位置,不是字符位置
    // 对于 UTF-8 中文字符(3字节),必须在字符边界切割
    let chinese = String::from("你好世界");
    let ni_hao = &chinese[0..6];  // "你好"(每个中文3字节)
    println!("中文切片: {}", ni_hao);
    // let bad = &chinese[0..1];  // panic!不在字符边界上

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

    // 【实用】first_word 示例
    let sentence = String::from("hello world");
    let word = first_word(&sentence);
    println!("第一个单词: {}", word);

    // 切片确保了引用的有效性
    // let mut s = String::from("hello world");
    // let word = first_word(&s);  // 不可变借用
    // s.clear();  // 错误!不能修改,因为 word 还在使用不可变引用
    // println!("{}", word);

    // ----------------------------------------------------------
    // 6. 借用的实际应用模式
    // ----------------------------------------------------------
    println!("\n=== 实际应用 ===");

    // 模式1: 只读访问用不可变引用
    let data = vec![1, 2, 3, 4, 5];
    let sum = sum_vec(&data);
    let max = max_vec(&data);
    println!("data={:?}, sum={}, max={}", data, sum, max);

    // 模式2: 需要修改用可变引用
    let mut scores = vec![85, 92, 78, 96, 88];
    add_bonus(&mut scores, 5);
    println!("加分后: {:?}", scores);

    // 模式3: 方法中的借用
    let text = String::from("Hello, Rust! Welcome to Rust programming.");
    let stats = TextStats::new(&text);
    println!("字符数={}, 单词数={}, 行数={}", stats.chars, stats.words, stats.lines);

    println!("\n=== 借用与引用结束 ===");
}

// ----------------------------------------------------------
// 不可变借用参数
// ----------------------------------------------------------
fn calculate_length(s: &String) -> usize {
    s.len()
} // s 只是引用,离开作用域不会释放数据

// ----------------------------------------------------------
// 可变借用参数
// ----------------------------------------------------------
fn change(s: &mut String) {
    s.push_str(", world");
}

// ----------------------------------------------------------
// 返回切片引用
// ----------------------------------------------------------
fn first_word(s: &str) -> &str {
    // 【技巧】参数类型用 &str 而不是 &String
    // 这样 String 和 &str 都能传入(更灵活)
    let bytes = s.as_bytes();
    for (i, &byte) in bytes.iter().enumerate() {
        if byte == b' ' {
            return &s[..i];
        }
    }
    s  // 没有空格,返回整个字符串
}

// ----------------------------------------------------------
// 实际应用函数
// ----------------------------------------------------------
fn sum_vec(v: &Vec<i32>) -> i32 {
    v.iter().sum()
}

fn max_vec(v: &Vec<i32>) -> i32 {
    *v.iter().max().unwrap()
}

fn add_bonus(scores: &mut Vec<i32>, bonus: i32) {
    for score in scores.iter_mut() {
        *score += bonus;
    }
}

// ----------------------------------------------------------
// 结构体中的借用模式
// ----------------------------------------------------------
struct TextStats {
    chars: usize,
    words: usize,
    lines: usize,
}

impl TextStats {
    fn new(text: &str) -> TextStats {
        TextStats {
            chars: text.chars().count(),
            words: text.split_whitespace().count(),
            lines: text.lines().count(),
        }
    }
}

💬 讨论

使用 GitHub 账号登录后即可参与讨论

基于 MIT 许可发布