Rust 的 引用与借用

在 Rust 中,若仅通过转移所有权获取值,会使程序复杂度增加。而"引用与借用"机制能像其他编程语言的指针或引用一样,让我们在不转移所有权的前提下使用变量,极大地提升了代码的灵活性与简洁性。本教程将详细讲解引用与借用的核心概念、用法及规则。

一、引用与解引用

1. 引用的定义

常规引用是一种指针类型,指向对象存储的内存地址。通过引用,我们可以访问变量的值,但不会获取变量的所有权。

2. 基础示例

rust 复制代码
fn main() {
    let x = 5;
    let y = &x; // 创建x的引用y

    assert_eq!(5, x);   // 直接访问x的值,断言成功
    assert_eq!(5, *y);  // 使用解引用运算符*获取y指向的值,断言成功
}
  • &x:表示创建变量x的引用,y的类型为&i32
  • *y:解引用运算符,用于获取引用y所指向的实际值。

3. 常见错误:未解引用的比较

若直接比较整数与引用,会因类型不匹配导致编译错误:

rust 复制代码
// 错误代码
fn main() {
    let x = 5;
    let y = &x;
    assert_eq!(5, y); // 错误:无法比较`i32`类型与`&i32`类型
}

编译错误信息:

复制代码
error[E0277]: can't compare `{integer}` with `&{integer}`
 --> src/main.rs:4:5
  |
4 |     assert_eq!(5, y);
  |     ^^^^^^^^^^^^^^^^^ no implementation for `{integer} == &{integer}`
  |
  = help: the trait `std::cmp::PartialEq<&{integer}>` is not implemented for `{integer}`

二、不可变引用

1. 概念与作用

不可变引用允许我们访问变量的值,但不允许修改该值,且不会获取变量的所有权。当引用离开作用域时,其指向的值不会被丢弃。

2. 函数传参示例

通过不可变引用向函数传递参数,避免所有权转移:

rust 复制代码
fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1); // 传递s1的不可变引用

    // 由于未转移所有权,s1仍可正常使用
    println!("The length of '{}' is {}.", s1, len);
}

// 函数参数为&String类型(String的不可变引用)
fn calculate_length(s: &String) -> usize {
    s.len() // 访问引用指向的值的长度,无需解引用(Rust自动解引用优化)
} // s离开作用域,因不拥有所有权,无任何资源释放操作

3. 错误尝试:修改不可变引用

若尝试通过不可变引用修改值,会触发编译错误:

rust 复制代码
// 错误代码
fn main() {
    let s = String::from("hello");
    change(&s); // 传递不可变引用
}

fn change(some_string: &String) {
    some_string.push_str(", world"); // 尝试修改不可变引用指向的值,错误
}

编译错误信息:

复制代码
error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference
 --> src/main.rs:8:5
  |
7 | fn change(some_string: &String) {
  |                        ------- help: consider changing this to be a mutable reference: `&mut String`
8 |     some_string.push_str(", world");
  |     ^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable

三、可变引用

1. 概念与用法

可变引用允许我们修改其指向的值,但需满足特定规则。使用可变引用需同时满足两个条件:

  1. 变量声明为mut(可变)。
  2. 引用通过&mut创建,函数参数类型需匹配为&mut T

2. 正确示例

rust 复制代码
fn main() {
    let mut s = String::from("hello"); // 变量s声明为可变
    change(&mut s); // 传递s的可变引用
    println!("{}", s); // 输出:hello, world
}

// 函数参数为&mut String(String的可变引用)
fn change(some_string: &mut String) {
    some_string.push_str(", world"); // 成功修改引用指向的值
}

3. 核心限制1:同一作用域仅一个可变引用

同一作用域内,特定数据只能有一个可变引用,此规则用于避免数据竞争:

rust 复制代码
// 错误代码
fn main() {
    let mut s = String::from("hello");
    let r1 = &mut s; // 第一个可变引用
    let r2 = &mut s; // 第二个可变引用,错误

    println!("{}, {}", r1, r2);
}

编译错误信息:

复制代码
error[E0499]: cannot borrow `s` as mutable more than once at a time
 --> src/main.rs:5:14
  |
4 |     let r1 = &mut s;
  |              ------ first mutable borrow occurs here
5 |     let r2 = &mut s;
  |              ^^^^^^ second mutable borrow occurs here
7 |     println!("{}, {}", r1, r2);
  |                        -- first borrow later used here

4. 解决方案:手动限制作用域

通过大括号{}手动划分作用域,可创建多个可变引用(非同一作用域):

rust 复制代码
fn main() {
    let mut s = String::from("hello");

    {
        let r1 = &mut s; // r1的作用域仅限于此大括号内
        println!("{}", r1); // 输出:hello
    } // r1离开作用域,可变引用释放

    let r2 = &mut s; // 可创建新的可变引用r2
    println!("{}", r2); // 输出:hello
}

5. 核心限制2:可变与不可变引用不能同时存在

若数据已存在不可变引用,同一作用域内不能再创建可变引用(避免不可变引用读取到被修改后的数据):

rust 复制代码
// 错误代码
fn main() {
    let mut s = String::from("hello");
    let r1 = &s;  // 不可变引用
    let r2 = &s;  // 另一个不可变引用(允许)
    let r3 = &mut s; // 可变引用,错误

    println!("{}, {}, and {}", r1, r2, r3);
}

编译错误信息:

复制代码
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; // 没问题
6 |     let r3 = &mut s; // 大问题
  |              ^^^^^^ mutable borrow occurs here
8 |     println!("{}, {}, and {}", r1, r2, r3);
  |                                -- immutable borrow later used here

6. 编译器优化:引用作用域缩短(NLL)

Rust 引入 Non-Lexical Lifetimes(NLL,非词法生命周期) 优化,使引用的作用域从"花括号结束"缩短为"最后一次使用的位置",提升代码灵活性:

rust 复制代码
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); // 输出:hello
}
  • 老版本编译器(Rust 1.31 前):r1r2作用域到函数结束,此代码会报错。
  • 新版本编译器:r1r2作用域在println!后结束,代码正常编译。

四、悬垂引用(Dangling References)

1. 概念

悬垂引用指指针指向的值被释放后,指针仍存在,可能指向无效内存或被其他变量复用,会导致未定义行为。Rust 编译器通过生命周期检查,确保引用永远不会悬垂

2. 错误示例:创建悬垂引用

rust 复制代码
// 错误代码
fn main() {
    let reference_to_nothing = dangle(); // 接收悬垂引用
}

// 尝试返回字符串的引用,会导致悬垂
fn dangle() -> &String {
    let s = String::from("hello"); // s在函数内创建
    &s // 返回s的引用
} // s离开作用域,被释放,引用指向无效内存

编译错误信息:

复制代码
error[E0106]: missing lifetime specifier
 --> src/main.rs:5:16
  |
5 | fn dangle() -> &String {
  |                ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime
  |
5 | fn dangle() -> &'static String {
  |                ~~~~~~~~

关键错误原因:函数返回借用值,但找不到其借用的原数据(原数据已被释放)

3. 解决方案:返回所有权而非引用

直接返回值的所有权,避免返回引用:

rust 复制代码
fn main() {
    let string = no_dangle(); // 接收String的所有权
    println!("{}", string); // 输出:hello
}

// 返回String而非引用,所有权转移给调用者
fn no_dangle() -> String {
    let s = String::from("hello");
    s // 返回s,转移所有权
}

五、借用规则总结

  1. 引用有效性:引用必须始终有效,不能指向已释放的数据(无悬垂引用)。
  2. 可变引用限制:同一时刻,特定数据只能有一个可变引用,或任意多个不可变引用(二选一)。
  3. 作用域优化:通过 NLL 优化,引用作用域缩短为最后一次使用的位置,提升代码灵活性。
相关推荐
永远都不秃头的程序员(互关)2 小时前
【K-Means深度探索(一)】数据炼金术第一步:从零手撕K-Means聚类算法
算法·kmeans·聚类
我想回家种地2 小时前
算法期末复习
算法
喵星人工作室2 小时前
C++传说:神明之剑0.4.5装备机制彻底完成
开发语言·c++·游戏
秦jh_2 小时前
【Qt】系统相关(下)
开发语言·qt
东木月2 小时前
使用python获取Windows产品标签
开发语言·windows·python
pumpkin845142 小时前
Go 基础语法全景
开发语言·后端·golang
hqwest2 小时前
码上通QT实战18--监控页面10-获取设备数据
开发语言·qt·湿度·modbus功能码·寄存器地址·从站数据·0103
rgeshfgreh2 小时前
MPPI算法实战:运动规划新利器
算法
Xの哲學2 小时前
Linux epoll 深度剖析: 从设计哲学到底层实现
linux·服务器·网络·算法·边缘计算