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

Rust:引用

Rust:引用

    • 引用
      • &T 不可变引用
      • &mut T 可变引用
    • 借用规则
      • Non-Lexical Lifetimes (NLL) 优化
    • 解引用
      • * 操作符
      • 多级引用
      • 自动解引用


引用

在C++中,传递参数时我们经常需要在值传递、指针传递、引用传递之间选择。Rust的引用系统在编译期就解决了很多C++运行时才能发现的内存安全问题。

&T 不可变引用

不可变引用允许你读取数据,但不能通过引用修改数据。

语法:

let 引用: &类型 = &变量;

示例:

let s: String = String::from("hello");
let r: &String = &s;println!("原始字符串: {}", s);
println!("引用内容: {}", r);

不可变引用不会获取所有权,原始变量仍然有效。通过&操作符创建引用,引用只是指向数据的"借用",不会转移所有权。

不可变引用不能修改数据

尝试通过不可变引用修改数据会导致编译错误:

let mut s = String::from("hello");
let r = &s;r.push_str(", world"); // 编译错误!

这个限制确保了数据的不变性。即使原始变量是mut的,通过不可变引用也无法修改数据,这是Rust类型系统的核心安全保证。

多个不可变引用可以同时存在

可以同时创建多个不可变引用:

let s = String::from("hello");let r1 = &s;
let r2 = &s;
let r3 = &s;println!("{}, {}, {}", r1, r2, r3);

多个不可变引用是安全的,因为它们都只是读取数据,不会造成数据竞争。读取操作本身是线程安全的。


&mut T 可变引用

可变引用允许你通过引用修改数据。

语法:

let 引用: &mut 类型 = &mut 变量;

示例:

let mut s = String::from("hello");
let r: &mut String = &mut s;r.push_str(", world");
println!("修改后: {}", r);

可变引用在其作用域内独占访问权。注意原始变量必须声明为mut才能创建可变引用。

可变引用存在时原始变量不可访问

当可变引用存在时,不能同时使用原始变量:

let mut s = String::from("hello");
let r = &mut s;println!("{}", s); // 编译错误!
r.push_str(", world");

以上代码中,r 获取了字符串的可变引用,同时用户使用了原始变量 s,编译器不允许这种行为,可变引用使用期间,独占所有权

这个限制防止了数据竞争。如果允许同时通过原始变量和可变引用访问数据,可能导致数据不一致。


借用规则

Rust的借用规则是其内存安全的核心,这些规则在编译期强制执行,防止数据竞争和内存安全问题。

规则1:同一时间只能有一个可变引用

正确的使用方式:

let mut s = String::from("hello");
let r1 = &mut s;r1.push_str(", world");
println!("{}", r1);

违反规则的错误示例:

let mut s = String::from("hello");let r1 = &mut s;
let r2 = &mut s; // 编译错误println!("{}, {}", r1, r2);

这个规则防止了数据竞争。如果允许多个可变引用同时存在,可能导致一个引用修改数据时,另一个引用读取到不一致的状态。

规则2:可变引用与不可变引用不能同时存在

正确的使用方式:

let mut s = String::from("hello");let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);

违反规则的错误示例:

let mut s = String::from("hello");let r1 = &s;
let r2 = &s;
let r3 = &mut s; // 编译错误!println!("{}, {}, {}", r1, r2, r3);

这个规则防止不可变引用的数据被意外修改。如果允许可变引用和不可变引用同时存在,不可变引用持有者期望数据不变,但可变引用可能会修改数据,导致不可变引用看到意外的数据变化。

规则3:引用的生命周期不能超过其引用的数据

正确的生命周期管理:

let mut s = String::from("hello");{let r1 = &s;println!("{}", r1);
} // r1 在这里超出作用域let r2 = &mut s;
r2.push_str(", world");
println!("{}", r2);

违反生命周期规则的错误示例:

let r;
{let x = 5;r = &x; // 编译错误:x 的生命周期不够长
}
println!("{}", r);

引用必须始终指向有效的内存。这个规则防止了悬垂引用,确保引用永远不会指向已经被释放的内存。


Non-Lexical Lifetimes (NLL) 优化

Rust 2018引入了更智能的借用检查,能够分析引用的实际使用范围:

let mut s = String::from("hello");let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
// r1 和 r2 在这里不再被使用,生命周期结束let r3 = &mut s; // 现在可以创建可变引用
println!("{}", r3);

编译器能够分析出r1r2println!之后不再使用,所以允许后续创建可变引用。这比简单的词法作用域更智能,提高了代码的灵活性。


解引用

* 操作符

使用*操作符可以访问引用指向的值:

let x = 5;
let y = &x;println!("x = {}", x);
println!("*y = {}", *y);

解引用操作符*获取引用指向的实际值,这是最基础的解引用方式。

引用和值是不同的类型,不能直接比较

let x = 5;
let y = &x;assert_eq!(5, y); // 编译错误:不能比较i32和&i32
assert_eq!(5, *y);   // 正确:解引用后比较

必须通过解引用将引用转换为值才能进行操作。


多级引用

可以创建指向引用的引用:

let x = 5;
let r1 = &x;      // r1: &i32
let r2 = &r1;     // r2: &&i32  
let r3 = &r2;     // r3: &&&i32println!("x = {}", x);
println!("*r1 = {}", *r1);      // 5
println!("**r2 = {}", **r2);    // 5  
println!("***r3 = {}", ***r3);  // 5

每增加一层引用,就需要增加一个*来解引用


自动解引用

  • 方法调用

在使用.调用方法时,Rust会自动解引用:

let s = String::from("hello");
let r1 = &s;
let r2 = &r1;
let r3 = &r2;// 以下调用都等价
println!("直接调用: {}", s.len());
println!("一级引用: {}", r1.len());      // 等价于 (*r1).len()
println!("二级引用: {}", r2.len());      // 等价于 (**r2).len()  
println!("三级引用: {}", r3.len());      // 等价于 (***r3).len()

方法调用会自动解引用到找到对应方法为止

  • 字段访问

访问结构体字段时也会自动解引用:

struct Point {x: i32,y: i32,
}fn main() {let point = Point { x: 10, y: 20 };let r1 = &point;let r2 = &r1;// 以下访问都等价println!("直接访问: ({}, {})", point.x, point.y);println!("一级引用: ({}, {})", r1.x, r1.y);        // 等价于 (*r1).x, (*r1).yprintln!("二级引用: ({}, {})", r2.x, r2.y);        // 等价于 (**r2).x, (**r2).y
}

如果把变量放在函数 () 内作为参数传入,那么此时必须手动解引用

let x = 5;
let r1 = &x;
let r2 = &r1;// 方法调用:自动解引用
let abs1 = x.abs();      // 直接调用
let abs2 = r1.abs();     // 自动解引用
let abs3 = r2.abs();     // 自动多重解引用// 函数调用:需要手动解引用  
let abs4 = i32::abs(x);     // 传值
let abs5 = i32::abs(*r1);   // 手动解引用
let abs6 = i32::abs(**r2);  // 手动多重解引用println!("方法调用结果: {}, {}, {}", abs1, abs2, abs3);
println!("函数调用结果: {}, {}, {}", abs4, abs5, abs6);

以上代码中,用两种不同的方式调用了相同的函数abs,一个需要手动解引用,另一个自动解引用。

关键区别

  • obj.function():编译器自动处理所有层级的解引用
  • function(*obj):需要手动解引用确保参数类型匹配

自动解引用是编译器自动完成的,编译器查找方法时的步骤:

let s = String::from("hello");
let r = &s;
let rr = &r;let len = rr.len();
println!("字符串长度: {}", len);

代码中,定义了一个二级引用 rr,并调用了 rr.len() 方法。此时编译器会进行尝试:

  1. rr.len() 不存在,rr 这个二级引用没有 len 方法
  2. *rr.len() 不存在,*rr 这个一级引用没有 len 方法
  3. **rr.len() 存在,**rr 这个Stringlen 方法

编译器会按照固定顺序尝试,一层一层解引用,直到找到匹配的方法,如果没有匹配的方法,就会报错


http://www.dtcms.com/a/353657.html

相关文章:

  • Vue-24-利用Vue3的element-plus库实现树形结构数据展示
  • Autodesk Maya 2026.2 全新功能详解:MotionMaker AI 动画、LookdevX 材质增强、USD 工作流优化
  • 在MiniOB源码中学习使用Flex与Bison解析SQL语句-第二节
  • 【Linux】正则表达式学习记录
  • FFMPEG api使用
  • 从disable_cost到disabled_nodes,最小代价预估质的飞跃
  • nestjs日志(nest-winston)
  • pyecharts可视化图表-tree:从入门到精通
  • Linux 系统调优与CPU-IO-网络内核参数调优
  • Task04: CAMEL框架中的多智能体系统(课程第三章剩余章节)
  • 大模型安全概述、LlamaFirewall
  • ESP8266:Arduino学习
  • 前端性能优化:从指标监控到全链路落地(2024最新实战指南)
  • 短视频矩阵管理软件推荐——小麦矩阵系统深度解析
  • 关于两视图相机几何关系
  • DevExpress WPF中文教程:如何将WPF数据网格绑定到本地集合?
  • 软件定义汽车(SDV)调试——如何做到 适配软件定义汽车(SDV)?(下)
  • vue新能源汽车销售平台的设计与实现(代码+数据库+LW)
  • 【Vue2✨】 Vue2 入门之旅(二):模板语法
  • Python异步编程:从理论到实战的完整指南
  • Qt---项目架构解读
  • BiLSTM-Attention分类预测+SHAP分析+特征依赖图!深度学习可解释分析,Matlab代码实现
  • 【GaussDB】深度解析:创建存储过程卡死且无法Kill会话的疑难排查
  • codeforces(1045)(div2)D. Sliding Tree
  • 装饰器模式(C++python)
  • 第十四章 Leaflet-Ant-Path 实现西气东输管线动态流向可视化
  • 源代码接入 1688 接口的详细指南
  • 【生产事故处理--kafka日志策略保留】
  • antv x6实现封装拖拽流程图配置(适用于工单流程、审批流程应用场景)
  • 使用Stone 3D快速制作第一人称视角在线小游戏