首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >08-Rust 教程 - 借用与引用

08-Rust 教程 - 借用与引用

作者头像
LarryLan
发布2026-04-13 14:55:07
发布2026-04-13 14:55:07
160
举报

借用与引用

借用:数据还是你的,我只是"借来看看"


🎬 引入

还记得所有权那篇讲的吗?Rust 里每个值只有一个所有者,所有权转移后原变量就不能用了。

但这样有个问题:如果我想临时用一下某个值,但不想拿走它的所有权,怎么办?

代码语言:javascript
复制
fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(s1);  // s1 的所有权被转移了
    println!("{} 的长度是 {}", s1, len);  // 错误!s1 已经不能用了
}

这时候就需要**借用(Borrowing)**了。借用就像你借朋友的书看:书还是朋友的,你只是临时看看,看完还回去。

今天咱们就来彻底搞懂 Rust 的借用和引用,这是 Rust 最核心也最容易懵的概念之一。准备好,我们要深入了!


📌 核心概念

引用:& 符号

引用就是"指向某个值的指针",但不获取所有权。

代码语言:javascript
复制
fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1);  // &s1 是引用
    println!("{} 的长度是 {}", s1, len);  // s1 还能用!
}

fn calculate_length(s: &String) -> usize {  // s 是引用
    s.len()
}

关键点

  • &String 表示"引用一个 String"
  • 函数参数用引用,不获取所有权
  • 原变量 s1 还能继续使用

可变引用:&mut

默认引用是不可变的(不能修改)。想修改?用 &mut

代码语言:javascript
复制
fn main() {
    let mut s = String::from("hello");
    change(&mut s);
    println!("{}", s);  // 输出:hello, world
}

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

注意

  • 原变量必须声明为 mut
  • 函数参数用 &mut T

借用规则:编译器的"三条铁律"

Rust 的借用检查器有三条核心规则:

  1. 任意时刻,要么只有一个可变引用,要么有多个不可变引用
  2. 引用必须始终有效(不能悬垂)
  3. 可变引用独占期间,原变量不能访问

规则 1 详解

代码语言:javascript
复制
let mut s = String::from("hello");

let r1 = &s;      // ✅ 不可变引用
let r2 = &s;      // ✅ 可以有多个不可变引用
let r3 = &mut s;  // ❌ 错误!同时存在可变和不可变引用
代码语言:javascript
复制
let mut s = String::from("hello");

let r1 = &mut s;  // ✅ 可变引用
let r2 = &mut s;  // ❌ 错误!只能有一个可变引用

规则 2 详解:引用不能比它指向的数据活得久(后面详细讲)。

规则 3 详解

代码语言:javascript
复制
let mut s = String::from("hello");

{
    let r1 = &mut s;
    r1.push_str(", world");
}  // r1 在这里离开作用域

let r2 = &mut s;  // ✅ 现在可以了,r1 已经"还回去"了

悬垂引用:引用了个"寂寞"

悬垂引用(Dangling Reference)是指引用指向的内存已经被释放

代码语言:javascript
复制
fn dangle() -> &String {
    let s = String::from("hello");
    &s  // 错误!s 在函数结束时被 drop 了
}

编译器错误

代码语言:javascript
复制
error[E0106]: missing lifetime specifier
 --> src/main.rs:1:16
  |
1 | fn dangle() -> &String {
  |                ^ expected named lifetime parameter

人话翻译:编译器:"你返回的引用指向谁?s 在函数结束时就没了,你让引用指向空气吗?"

修复:返回所有权,而不是引用。

代码语言:javascript
复制
fn no_dangle() -> String {
    let s = String::from("hello");
    s  // 返回所有权
}

切片:引用的"范围视图"

切片(Slice)是对集合中连续范围的引用

代码语言:javascript
复制
let s = String::from("hello world");

let hello = &s[..];   // "hello"
let world = &s[..];  // "world"

切片类型

  • 字符串切片:&str
  • 数组切片:&[T]
代码语言:javascript
复制
let arr = [, , , , ];
let slice = &arr[..];  // &[2, 3],类型是 &[i32]

重点:切片不拥有数据,它只是引用。


💻 代码示例

基础示例:不可变引用

代码语言:javascript
复制
fn main() {
    let s = String::from("hello");
    
    let r1 = &s;
    let r2 = &s;
    
    println!("{} and {}", r1, r2);
    // r1 和 r2 离开作用域
    // s 还能用!
    println!("{}", s);
}

错误示例 1:同时存在可变和不可变引用

代码语言:javascript
复制
fn main() {
    let mut s = String::from("hello");
    
    let r1 = &s;
    let r2 = &s;
    let r3 = &mut s;  // ❌ 错误
    
    println!("{}, {}, and {}", r1, r2, r3);
}

编译器错误

代码语言:javascript
复制
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
 --> src/main.rs:6:14
  |
4 |     let r1 = &s;
  |              -- immutable borrow occurs here
5 |     let r2 = &s;
  |              -- immutable borrow occurs here
6 |     let r3 = &mut s;
  |              ^^^^^^ mutable borrow occurs here
7 |
8 |     println!("{}, {}, and {}", r1, r2, r3);
  |                                -- immutable borrows used here

人话翻译:编译器:"s 已经被 r1r2 不可变借用了,你又想可变借用?不行!要么大家只读,要么你一个人写,不能同时!"

修复:让不可变引用先结束。

代码语言:javascript
复制
fn main() {
    let mut s = String::from("hello");
    
    {
        let r1 = &s;
        let r2 = &s;
        println!("{} and {}", r1, r2);
    }  // r1 和 r2 在这里结束
    
    let r3 = &mut s;  // ✅ 现在可以了
    println!("{}", r3);
}

错误示例 2:可变引用期间访问原变量

代码语言:javascript
复制
fn main() {
    let mut s = String::from("hello");
    
    let r1 = &mut s;
    r1.push_str(", world");
    
    println!("{}", s);  // ❌ 错误!r1 还在借用中
}

编译器错误

代码语言:javascript
复制
error[E0502]: cannot borrow `s` as immutable because it is also borrowed as mutable
 --> src/main.rs:7:20
  |
5 |     let r1 = &mut s;
  |              ------ mutable borrow occurs here
6 |     r1.push_str(", world");
7 |     println!("{}", s);
  |                    ^ immutable borrow occurs here
8 | }
  | - mutable borrow ends here

人话翻译:编译器:"r1 正在可变借用 s,你又想访问 s?万一 r1 还在改怎么办?等 r1 用完再说!"

修复

代码语言:javascript
复制
fn main() {
    let mut s = String::from("hello");
    
    {
        let r1 = &mut s;
        r1.push_str(", world");
    }  // r1 结束
    
    println!("{}", s);  // ✅ 现在可以了
}

错误示例 3:悬垂引用

代码语言:javascript
复制
fn get_first_word(s: &String) -> &str {
    let word = String::from("hello");
    &word  // ❌ 错误!word 在函数结束时被 drop
}

编译器错误

代码语言:javascript
复制
error[E0515]: cannot return reference to local variable `word`
 --> src/main.rs:3:5
  |
3 |     &word
  |     ^^^^^ returns a reference to data owned by the current function

人话翻译:编译器:"你返回的引用指向 word,但 word 是函数里的局部变量,函数结束就没了。你这是想让人家引用个寂寞?"

修复:返回切片(引用传入的字符串)。

代码语言:javascript
复制
fn get_first_word(s: &String) -> &str {
    let bytes = s.as_bytes();
    
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[..i];
        }
    }
    
    &s[..]  // 返回整个字符串的切片
}

🐛 常见坑点

坑点 1:忘记 mut

代码语言:javascript
复制
let s = String::from("hello");
let r = &mut s;  // 错误!s 不是 mut

修复let mut s = ...

坑点 2:多个可变引用

代码语言:javascript
复制
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;  // 错误!

修复:用完一个再借用下一个。

代码语言:javascript
复制
let mut s = String::from("hello");
{
    let r1 = &mut s;
    // 用 r1
}
let r2 = &mut s;  // ✅

坑点 3:切片越界

代码语言:javascript
复制
let s = String::from("hello");
let slice = &s[..];  // panic!

修复:确保索引在范围内。

代码语言:javascript
复制
let slice = &s[..s.len()];  // ✅

坑点 4:&String vs &str

代码语言:javascript
复制
fn print_string(s: &String) {
    println!("{}", s);
}

fn main() {
    let s = String::from("hello");
    print_string(&s);  // ✅
    
    let literal = "hello";
    print_string(&literal);  // ❌ 错误!&str != &String
}

修复:用 &str 更灵活。

代码语言:javascript
复制
fn print_string(s: &str) {
    println!("{}", s);
}

🎯 实战案例

案例 1:字符串处理函数

代码语言:javascript
复制
fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[..i];
        }
    }
    
    &s[..]
}

fn main() {
    let my_string = String::from("hello world");
    let word = first_word(&my_string);
    println!("第一个单词:{}", word);
    
    let my_literal = "hello world";
    let word = first_word(my_literal);  // 也能用!
    println!("第一个单词:{}", word);
}

案例 2:修改数组

代码语言:javascript
复制
fn double_all(nums: &mut [i32]) {
    for num in nums.iter_mut() {
        *num *= ;
    }
}

fn main() {
    let mut nums = [, , , , ];
    double_all(&mut nums);
    println!("{:?}", nums);  // [2, 4, 6, 8, 10]
}

案例 3:借用和所有权的组合

代码语言:javascript
复制
#[derive(Debug)]
struct User {
    name: String,
    age: u32,
}

fn greet(user: &User) {
    println!("你好,{}!", user.name);
}

fn have_birthday(user: &mut User) {
    user.age += ;
}

fn main() {
    let mut user = User {
        name: String::from("Alice"),
        age: ,
    };
    
    greet(&user);  // 借用
    have_birthday(&mut user);  // 可变借用
    greet(&user);  // 还能用!
    
    println!("{:?}", user);
}

🧠 思维导图

08-借用与引用
08-借用与引用

📝 小结

  1. 引用是"借来看看",不获取所有权,用 & 创建
  2. 借用规则:要么一个 &mut,要么多个 &,不能同时
  3. 可变引用独占期间,原变量不能访问
  4. 悬垂引用编译时检查,Rust 不允许引用指向无效内存
  5. 切片是范围的引用&str&[T] 最常用

下篇预告:字符串在 Rust 里是个"坑货",String&str 傻傻分不清?下一篇专门聊聊字符串,彻底搞懂这对"双胞胎"!


🔗 参考资料

  • Rust Book - 引用和借用
  • Rust By Example - 借用
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-04-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Larry的Hub 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 借用与引用
    • 🎬 引入
    • 📌 核心概念
      • 引用:& 符号
      • 可变引用:&mut
      • 借用规则:编译器的"三条铁律"
      • 悬垂引用:引用了个"寂寞"
      • 切片:引用的"范围视图"
    • 💻 代码示例
      • 基础示例:不可变引用
      • 错误示例 1:同时存在可变和不可变引用
      • 错误示例 2:可变引用期间访问原变量
      • 错误示例 3:悬垂引用
    • 🐛 常见坑点
      • 坑点 1:忘记 mut
      • 坑点 2:多个可变引用
      • 坑点 3:切片越界
      • 坑点 4:&String vs &str
    • 🎯 实战案例
      • 案例 1:字符串处理函数
      • 案例 2:修改数组
      • 案例 3:借用和所有权的组合
    • 🧠 思维导图
    • 📝 小结
    • 🔗 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档