当前位置: 首页 > news >正文

Rust 学习(7):所有权系统(下)-引用与借用

文章目录

    • 一、回顾与导入
    • 二、引用的基本概念
      • 2.1 不可变引用(Immutable Reference)
      • 2.2 引用 vs 解引用
    • 三、可变引用(Mutable Reference)
    • 四、借用规则(Borrowing Rules)
      • 规则 1:可变引用在同一作用域内只能有一个
      • 规则 2:不可变引用可以有多个,但不能与可变引用共存
      • 规则 3:引用的作用域持续到最后一次使用
      • 表格总结
    • 五、悬垂引用(Dangling References)
    • 六、引用的类型总结
    • 七、完整代码示例
    • 八、常见陷阱与最佳实践
      • 陷阱 1:误用 `&` 和 `&mut`
      • 陷阱 2:在循环中创建可变引用
      • 最佳实践
    • 九、总结与速查
    • 十、思考题
    • 参考链接

返回首页 | 上篇 | 下篇


掌握引用的使用 —— 借用、可变引用、悬垂引用与借用规则


一、回顾与导入

上篇文章我们学习了所有权的核心规则:

  • 每个值有唯一所有者
  • 赋值/传参会转移所有权(移动语义)
  • 离开作用域自动释放内存

但这带来了一个问题:如果我只是想读取一个值,而不想获取它的所有权,难道每次都要传入再返回吗?太繁琐了!

// 上篇的繁琐写法fnmain(){lets1=String::from("hello");let(s2,len)=calculate_length(s1);println!("'{}'的长度是 {}",s2,len);}fncalculate_length(s:String)->(String,usize){letlength=s.len();(s,length)}

Rust 提供了优雅的解决方案:引用(Reference)借用(Borrowing)


二、引用的基本概念

引用允许你使用值但不获取所有权,就像看一本书时可以借阅而不需要买下来。

2.1 不可变引用(Immutable Reference)

使用&符号创建引用:

fnmain(){lets1=String::from("hello");letlen=calculate_length(&s1);// &s1 创建对 s1 的引用println!("'{}'的长度是 {}",s1,len);// s1 仍然可用!}fncalculate_length(s:&String)->usize{s.len()// 使用引用访问值}// s 是引用,离开作用域不会 drop 原值

内存模型

s1 (栈) 堆 +---------+--------+----------+ +---------+---------+---------+---------+---------+ | ptr | len | capacity | | h | e | l | l | o | +---------+--------+----------+ +---------+---------+---------+---------+---------+ ↑ | &s1 (栈) +---------+ | ptr | → 指向 s1 +---------+

关键点

  • 引用不会获取所有权
  • 原变量s1仍然拥有数据
  • 引用离开作用域不会释放数据

示例:

letx=42;// x 在栈上letr=&x;// r 在栈上,存的是 x 的地址(指向栈)letb=Box::new(42);// 堆上存 42,栈上存 b(指向堆)letr=&b;// r 在栈上,存的是 b 的地址(指向栈)// 间接指向堆上的 42// 更直接:letv=vec![1,2,3];// 数据在堆上letr=&v[0];// r 指向堆上的第一个元素

内存示意图:

栈地址 内容 0x1000: x = 42 0x1008: r = 0x1000 (指向 x) 栈地址 内容 0x2000: b = 0x5000 (Box,指向堆) 0x2008: r = 0x2000 (指向 b) 堆地址 内容 0x5000: 42

2.2 引用 vs 解引用

概念符号含义
创建引用&value创建对值的引用
解引用*ref获取引用指向的值
fnmain(){letx=5;lety=&x;// y 是引用assert_eq!(5,x);assert_eq!(5,*y);// *y 解引用取得值}

三、可变引用(Mutable Reference)

如果需要修改引用的值,可以使用可变引用&mut

fnmain(){letmuts=String::from("hello");change(&muts);// 传递可变引用println!("{}",s);// hello, world}fnchange(some_string:&mutString){some_string.push_str(", world");}

关键规则

  • 原变量必须是mut声明的
  • 引用类型必须是&mut
  • 可变引用有严格的使用限制(见下文)

四、借用规则(Borrowing Rules)

Rust 编译器对引用的使用施加了严格的规则,确保内存安全

规则 1:可变引用在同一作用域内只能有一个

fnmain(){letmuts=String::from("hello");letr1=&muts;letr2=&muts;// ❌ 编译错误:不能同时存在两个可变引用println!("{}, {}",r1,r2);}

为什么?防止数据竞争(Data Race)。

规则 2:不可变引用可以有多个,但不能与可变引用共存

fnmain(){letmuts=String::from("hello");letr1=&s;// 不可变引用letr2=&s;// 不可变引用(多个没问题)letr3=&muts;// ❌ 编译错误:已有不可变引用时不能有可变引用println!("{}, {}",r1,r2);}

因为:不可变引用希望数据不被修改,可变引用允许修改,两者同时存在会造成冲突。

规则 3:引用的作用域持续到最后一次使用

fnmain(){letmuts=String::from("hello");letr1=&s;// 不可变引用开始letr2=&s;// 不可变引用开始println!("{} {}",r1,r2);// 不可变引用最后使用// r1, r2 的作用域到此结束letr3=&muts;// ✅ 可以,因为前面的不可变引用已失效println!("{}",r3);}

表格总结

场景是否允许说明
多个不可变引用同时读取是安全的
一个可变引用唯一写入权
可变引用 + 不可变引用共存读写冲突
多个可变引用写入冲突

五、悬垂引用(Dangling References)

悬垂引用指向的内存已经被释放。Rust 编译器会在编译期阻止悬垂引用的产生

fnmain(){letreference_to_nothing=dangle();}fndangle()->&String{// ❌ 编译错误lets=String::from("hello");&s// 返回 s 的引用}// s 离开作用域,被 drop,引用指向已释放的内存

错误信息:

error[E0106]: missing lifetime specifier --> 5 | fn dangle() -> &String { | ^ expected named lifetime parameter

解决方案:返回String本身(转移所有权)而不是引用:

fnno_dangle()->String{lets=String::from("hello");s// 所有权转移出去}

六、引用的类型总结

引用类型语法能否修改原值能否复制限制
不可变引用&T✅(引用本身可复制)可以有多个
可变引用&mut T同一时刻只能有一个
裸指针*const T/*mut T视情况不安全,需 unsafe

七、完整代码示例

fnmain(){println!("=== 1. 不可变引用 ===");lets1=String::from("Rust");letlen=get_length(&s1);println!("'{}' 长度 = {}, s1 仍然可用: {}",s1,len,s1);println!("\n=== 2. 可变引用 ===");letmuts2=String::from("Hello");println!("修改前: {}",s2);append_world(&muts2);println!("修改后: {}",s2);println!("\n=== 3. 多个不可变引用 ===");lets3=String::from("Multiple");letr1=&s3;letr2=&s3;letr3=&s3;println!("三个引用指向: {}, {}, {}",r1,r2,r3);println!("\n=== 4. 可变引用不可共存示例 ===");letmuts4=String::from("test");letr1=&s4;letr2=&s4;println!("r1 = {}, r2 = {}",r1,r2);// r1 和 r2 在这之后不再使用letr3=&muts4;// ✅ 可以,因为前面的引用不再使用r3.push_str("_mutated");println!("r3 = {}",r3);println!("\n=== 5. 解引用 ===");letx=42;lety=&x;println!("x = {}, y = {}, *y = {}",x,y,*y);}fnget_length(s:&String)->usize{s.len()// 通过引用访问}fnappend_world(s:&mutString){s.push_str(", world!");// 通过可变引用修改}

println!是宏,它只是读取 s2,并没有转移所有权。

println! 宏会自动对参数取引用,等价于:

println!("{}",&s2);// 传递的是 &String,不是 String

八、常见陷阱与最佳实践

陷阱 1:误用&&mut

fnmain(){letmuts=String::from("hello");letr=&s;// 不可变引用// r.push_str("!"); // ❌ 不可变引用不能修改letm=&muts;// 可变引用m.push_str("!");}

陷阱 2:在循环中创建可变引用

fnmain(){letmutv=vec![1,2,3];// ❌ 编译错误:不能同时在循环中持有可变引用自己// for item in &mut v {// v.push(*item + 1);// }// ✅ 正确做法:先收集,再修改letnew_elements:Vec<i32>=v.iter().map(|x|x+1).collect();v.extend(new_elements);}

最佳实践

场景推荐做法
只需要读取数据使用&T不可变引用
需要修改数据使用&mut T可变引用
需要同时读取多个地方使用多个&T
需要修改且无关顺序使用&mut T,注意作用域
不确定用哪种优先用不可变引用

九、总结与速查

概念语法特性
引用&value不获取所有权
可变引用&mut value可以修改原值
解引用*ref获取引用指向的值
借用使用引用的行为遵循借用规则

借用规则回顾

  1. 可变引用在同一作用域内只能有一个
  2. 可变引用和不可变引用不能共存
  3. 引用必须在有效期内(不能悬垂)

核心要点

  • ✅ 引用让函数可以安全地使用值而不获取所有权
  • ✅ 默认使用不可变引用(&T),需要修改时使用&mut T
  • ✅ 编译器严格执行借用规则,防止数据竞争
  • ✅ 引用的作用域是从创建到最后一次使用

十、思考题

  1. 以下代码能编译通过吗?为什么?
fnmain(){letmuts=String::from("hello");letr1=&s;letr2=&s;letr3=&muts;println!("{}, {}, {}",r1,r2,r3);}
  1. 以下代码的输出是什么?
fnmain(){letmutx=10;lety=&mutx;*y+=5;println!("{}",x);}
  1. 为什么 Rust 不允许同时有可变引用和不可变引用?

参考链接

  • Rust Book - References and Borrowing
  • Rust Reference - References
  • Rust by Example - Borrowing

返回首页 | 上篇 | 下篇

http://www.cnnetsun.cn/news/3415677.html

相关文章:

  • C++实现AI算法:从线性回归到性能优化与工程化实践
  • 文本生成工具从单次验证到批量稳定运行的实践指南
  • C++类对象成员:组合关系、初始化列表与生命周期管理详解
  • STM32+ESP8266+MQTT:从传感器到云端的物联网数据链路实战
  • 算法入职配置安装环境
  • 从指令集到应用生态:深度解析CPU、操作系统与软件的协同进化
  • MATLAB与STK互联17:卫星对象操作(8)—数据提供者进阶(DataProviders的批量获取与自动化处理)
  • [错误分析][Error]no match for ‘operator<<’:当右值遇上非const引用
  • C++并发编程实战:从标准库原语到性能优化避坑指南
  • YOLO26导出到CoreML和TFLite:DFL移除后的顺畅跨平台部署体验
  • 虚拟机liunxIP修改(VMware)
  • 5GNR TS38.101, 104 Channel Raster频率栅格与NR-ARFCN
  • Multisim仿真实战:楼道触摸延时开关电路设计与调试指南
  • ChatGPT Sites:将提示词转化为可共享Web应用的全新解决方案
  • C++哈希表自定义键类型:从std::pair到自定义类的哈希函数实现与性能优化
  • 线性动态规划-火车运输 、简单的LIS问题【算法赛】、划分、明日方舟大作战!
  • Cesium海量地下管线与倾斜摄影模型融合加载与性能优化
  • 从“写代码“到“审代码“:AI编程时代开发者的新角色
  • k8s/RainBond 配置容器 lifecycle,实现容器启动后执行自定义命令
  • VTK C++三维可视化入门:从管线原理到多面锥体渲染实战
  • Java程序设计(第3版)第四章——内部类
  • Claude Code终端环境深度配置指南:Node.js、环境变量与终端协同
  • Claude Opus 4.8 企业级应用场景落地指南
  • Trae AI IDE:全栈开发的自然语言控制台
  • C++实现INT4模型量化推理:6步跃迁法实战指南
  • MLCC选型指南:避免常见误区与提升电路可靠性
  • Cherry Studio本地知识库5分钟上手指南
  • C++二叉树遍历全解析:从递归到非递归,掌握核心算法与内存管理
  • 重大通知!!2026年十堰中级职称申报时间已出,切勿错过!!附上超全十堰职称申报指南
  • Typora 嵌入视频、音频