探索Rust语言之引用

文章目录

概述

Rust语言定义了两种引用类型:

  • 不可变引用:也称作共享引用,可以读取引用的值,但是不能修改。好处是可以同时持有某个值的任意多个共享引用;
  • 可变引用:可以读取和修改引用的值,但同一时刻不能拥有对该值的任何其它引用。

引用的定义

复制代码
    let mut a: i32 = 5;

    let a_ref: &i32 = &a;   // 创建不可变引用
    let a_mref: &mut i32 = &mut a;  // 创建可变引用

引用的规则

Rust引用需要遵循以下基本规则:

  • 规则1:在任意给定时间,要么只能有一个可变引用,要么只能有多个不可变引用;
  • 规则2:引用必须总是有效的。

Deref特型

Deref特型用于自定义解引用操作符(*)的行为。通过实现Deref特型将某类型像引用一样处理:

复制代码
trait Deref{
      type Target:?Sized;
      fn deref(&self) -> &Self::Target;
}

trait DerefMut: Deref{
      fn deref_mut(&mut self) -> &mut Self::Target;
}

隐式解引用规则

编译器发现不能够编译通过时,会在三种情况下尝试进行隐式解引用,所有的三种情况如下:

  • &T转为&U,其中T: Deref<Target=U>
  • &mut T转为&mut U,其中T: DerefMut<Target=U>
  • &mut T转为&U,其中T: Deref<Target=U>

.操作符

在Rust中,引用是通过&操作符现实创建的,而解引用也要显式使用*操作符:

复制代码
// 从这里开始都是Rust代码了
let x = 10;
let r = &x;             // &x是对x的共享引用
assert!(*r == 10);      // 显式地对r解引用

由于 Rust 代码中会广泛使用引用,因此 . 操作符会在必要时对其左操作数进行隐式解引用:

复制代码
struct Anime { name: &'static str, bechdel_pass: bool };
let aria = Anime { name: "Aria: The Animation", bechdel_pass: true };
let anime_ref = &aria;
assert_eq!(anime_ref.name, "Aria: The Animation");

除了隐式解引用,. 操作符在方法调用需要时还可以隐式借用对其左操作数的引用。

复制代码
let mut v = vec![1973, 1968];
v.sort();           // 隐式借用了对v的可修改引用
(&mut v).sort();    // 等价

在Rust 中,则必须使用 &和* 操作符来创建和追随引用,只有 . 操作符例外,它会隐式地借用和解引用。

引用的生命周期

Rust会给程序中的每个引用类型附加一个生命周期(类似于作用域),生命周期的范围与如何使用该引用相匹配。所谓NLL生命周期表示形式就是从借用时间开始,到你最后一次使用它结束(当然泛型作用域生命周期例外)。

变量的生命周期必须包含或涵盖该变量引用的生命周期:

保存在变量中的引用,其类型必须保证它在变量的整个生命周期内都有效,自初始化始,至离开作用域止。

通过链表探索引用的生命周期

在其它语言中,实现链表是一件很简单的事情,但是因为所有权机制的存在,Rust实现链表可谓困难重重,甚至有本书(《Too Many Linked Lists》)专门来讲解如何在Rust中实现链表。

使用前后节点指针

我们在实现链表的插入/删除操作时,通常会使用到当前节点的前节点指针或者后节点指针。我们参考其它语言的逻辑,对应的Rust代码如下:

复制代码
let mut cur = &mut slef.head;
let mut prev;

while let Some(node) = cur {
	prev = cur;
	cur = &mut node.next;	// node是对cur的再借用,在node生命周期内,不能使用cur
}

当然,Rust编译器拒绝了这种代码,报错如下:

由于node是对cur的可变再借用,在node生命周期内不能使用cur变量,因此prev = cur;违背了所有权规则。

查找并修改指定节点

复制代码
    let mut cur = &mut self.head;
    while let Some(node) = cur {
        if node.data != val {
            cur = &mut node.next;
        }
    }
    
    // do something

编译报错:

相关参考

  • 《Rust程序语言》
  • 《Rust程序语言设计》
相关推荐
程序猿DD2 小时前
OctaFuse Gateway 2.15.0:MCP 聚合、向量 Embeddings 与官方目录同步
后端·agent
余槐i5 小时前
PostgreSQL 函数索引报错 must be immutable:3种绕法与代价
数据库·后端·sql·postgresql·性能优化
需要8265 小时前
Spring Bean 生命周期:从实例化到销毁的完整钩子
java·后端·spring
死磕音视频6 小时前
让小模型做选择题:限定候选项并读取 logits 概率的入门实践(附 Python 代码)
开发语言·python·大模型·文本分类·transformers
凤山老林6 小时前
Spring Boot 接入国密 SM2/SM3/SM4 的一些实践记录
spring boot·后端·python
伯恩bourne7 小时前
Spring Boot Bean 注册的两种方式:@Component 家族 vs @Configuration + @Bean
java·spring boot·后端
张彦峰ZYF7 小时前
停顿短不等于延迟低:G1、ZGC 与低延迟服务的目标分野、代价账本与选型标定
后端·回收暂停、应用停止总时长·到达安全点、分配停滞·屏障吃吞吐、并发线程吃核心·并发窗口吃堆余量·分区式靠挑回收集兑现目标·染色指针式不支持压缩引用
pride.li8 小时前
第03章 Python 教程-基础语法:注释与变量
服务器·前端·后端·python·算法·计算机视觉
顽疲8 小时前
Java vue 养老系统源码详解:月账单 care_month_bill 与 Quartz 扣费 Job 实践
java·开发语言·vue.js·vue养老·springboot养老·java养老
Wang's Blog8 小时前
Java 项目实战: 交友项目 - 安装开发工具与环境准备
java·开发语言·交友