rust-参考与借用

参考与借用

在清单4-5中的元组代码的问题在于,我们必须将String返回给调用函数,这样我们才能在调用calculate_length之后继续使用String,因为String已经被移动到了calculate_length中。相反,我们可以提供一个对String值的引用。引用类似于指针,它是一个地址,我们可以沿着它访问存储在该地址的数据;这些数据由其他某个变量拥有。与指针不同的是,引用在引用的生命周期内保证指向一个特定类型的有效值。

以下是如何定义和使用一个以对象的引用作为参数而不是获取值的所有权的calculate_length函数:

文件名:src/main.rs

fn main() {let s1 = String::from("hello");let len = calculate_length(&s1);println!("'{}'的长度是{}。", s1, len);
}
fn calculate_length(s: &String) -> usize {s.len()
}

首先,注意变量声明和函数返回值中的所有元组代码都消失了。其次,注意我们将&s1传递给calculate_length,在其定义中,我们接受&String而不是String。这些&符号代表引用,它们允许你在不获取所有权的情况下引用某个值。图4-6展示了这一概念。
4-6

图4-6:一个指向String s1&String s的示意图

注意:使用&来引用的相反操作是解引用,这可以通过解引用运算符*来完成。我们将在第8章看到一些解引用运算符的用法,并在第15章详细讨论解引用的细节。

让我们更仔细地看看这里的函数调用:

let s1 = String::from("hello");let len = calculate_length(&s1);

&s1的语法让我们创建了一个引用,它指向s1的值,但并不拥有它。因为引用并不拥有它,所以当引用不再被使用时,它所指向的值不会被释放。

同样,函数的签名使用&来表明参数s的类型是一个引用。我们来添加一些解释性的注释:

fn calculate_length(s: &String) -> usize { // s是一个指向String的引用s.len()
} // 在这里,s的作用域结束了。但由于s并不拥有它所引用的内容,所以值不会被释放。

变量s的有效作用域与任何函数参数的作用域相同,但引用所指向的值在s不再被使用时不会被释放,因为s并不拥有它。当函数的参数是引用而不是实际值时,我们不需要返回值以交还所有权,因为我们从未拥有过所有权。

我们将创建引用的行为称为借用。就像在现实生活中一样,如果一个人拥有某样东西,你可以从他那里借来。当你用完后,你必须归还。你并不拥有它。

那么,如果我们尝试修改我们正在借用的内容会发生什么呢?尝试清单4-6中的代码。剧透警告:它无法工作!

文件名:src/main.rs

这段代码无法编译!

fn main() {let s = String::from("hello");change(&s);
}fn change(some_string: &String) {some_string.push_str(", world");
}

清单4-6:尝试修改一个被借用的值

这是错误信息:

$ cargo runCompiling ownership v0.1.0 (file:///projects/ownership)
error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference--> src/main.rs:8:5|
8 |     some_string.push_str(", world");|     ^^^^^^^^^^^ `some_string`是一个`&`引用,因此它所引用的数据不能被借用为可变的|
help: 考虑将其改为可变引用|
7 | fn change(some_string: &mut String) {|                         +++

有关此错误的更多信息,请尝试rustc --explain E0596

错误:由于之前的1个错误,无法编译ownership(二进制文件“ownership”)

正如变量默认是不可变的一样,引用也是不可变的。我们不允许修改我们引用的内容。

可变引用

我们可以通过一些小的调整来修复清单4-6中的代码,从而允许我们修改一个被借用的值。这些调整使用的是可变引用:

文件名:src/main.rs

fn main() {let mut s = String::from("hello");change(&mut s);
}fn change(some_string: &mut String) {some_string.push_str(", world");
}

首先,我们将s改为mut。然后,我们在调用change函数时使用&mut s创建一个可变引用,并更新函数签名以接受一个可变引用some_string: &mut String。这使得change函数将修改它借用的值这一行为变得非常清晰。

可变引用有一个重要的限制:如果你有一个对某个值的可变引用,那么你不能有其他对该值的引用。这段尝试为s创建两个可变引用的代码将无法通过编译:

文件名:src/main.rs

这段代码无法编译!

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

这是错误信息:

$ cargo runCompiling ownership v0.1.0 (file:///projects/ownership)
error[E0499]: cannot borrow `s` as mutable more than once at a time--> src/main.rs:5:14|
4 |     let r1 = &mut s;|              ------ 第一个可变借用发生在这里
5 |     let r2 = &mut s;|              ^^^^^^ 第二个可变借用发生在这里
6 |
7 |     println!("{}, {}", r1, r2);|                        -- 第一个借用在此处后续使用有关此错误的更多信息,请尝试`rustc --explain E0499`。
错误:由于之前的1个错误,无法编译`ownership`(二进制文件“ownership”)

这个错误表明这段代码是无效的,因为我们不能同时多次将s借用为可变的。第一个可变借用在r1中,必须持续到它在println!中被使用为止,但在那个可变引用创建和使用之间,我们试图在r2中创建另一个可变引用,它与r1借用相同的数据。

防止同时对相同数据进行多次可变引用的限制允许进行修改,但这种修改是受到严格控制的。新Rustaceans(Rust新手)常常会在这个问题上挣扎,因为大多数语言允许你在任何时候进行修改。这种限制的好处是Rust可以在编译时防止数据竞争。数据竞争类似于竞态条件,当出现以下三种行为时就会发生:

  • 两个或更多的指针同时访问相同的数据。
  • 至少有一个指针被用来写入数据。
  • 没有机制被用来同步对数据的访问。

数据竞争会导致未定义行为,并且在运行时试图追踪它们时很难诊断和修复;Rust通过拒绝编译存在数据竞争的代码来防止这个问题!

正如我们总是可以使用大括号来创建一个新作用域一样,我们可以允许有多个可变引用,只要它们不是同时存在的即可:

let mut s = String::from("hello");{let r1 = &mut s;
} // r1在这里结束作用域,因此我们可以毫无问题地创建一个新的引用。let r2 = &mut s;

Rust对组合可变引用和不可变引用也执行类似的规则。这段代码会导致错误:

这段代码无法编译!

let mut s = String::from("hello");let r1 = &s; // 没有问题
let r2 = &s; // 没有问题
let r3 = &mut s; // 大问题println!("{}, {}, and {}", r1, r2, r3);

这是错误信息:

$ cargo runCompiling ownership v0.1.0 (file:///projects/ownership)
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable--> src/main.rs:6:14|
4 |     let r1 = &s; // 没有问题|              -- 不可变借用发生在这里
5 |     let r2 = &s; // 没有问题
6 |     let r3 = &mut s; // 大问题|              ^^^^^^ 可变借用发生在这里
7 |
8 |     println!("{}, {}, and {}", r1, r2, r3);|                                -- 不可变借用在此处后续使用有关此错误的更多信息,请尝试`rustc --explain E0502`。
错误:由于之前的1个错误,无法编译`ownership`(二进制文件“ownership”)

呼!我们也不能在有对同一个值的不可变引用的同时拥有一个可变引用。

不可变引用的使用者不会期望值在他们不知情的情况下突然改变!然而,允许多个不可变引用是合理的,因为那些只是读取数据的人无法影响其他人的读取。

注意,引用的作用域从它被引入的地方开始,并持续到最后一次使用该引用的地方。例如,这段代码可以编译,因为不可变引用的最后一次使用是在println!中,这在可变引用被引入之前:

let mut s = String::from("hello");let r1 = &s; // 没有问题
let r2 = &s; // 没有问题
println!("{r1} and {r2}");
// 变量r1和r2在此点之后将不再被使用。let r3 = &mut s; // 没有问题
println!("{r3}");

不可变引用r1r2的作用域在它们最后一次被使用的println!之后结束,这在可变引用r3被创建之前。这些作用域没有重叠,所以这段代码是被允许的:编译器可以判断出在作用域结束之前引用不再被使用。

接下来,我们将看看另一种引用类型:切片。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/92845.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/92845.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入解析HDFS Federation:如何有效解决单NameNode瓶颈问题

HDFS Federation简介与背景在Hadoop分布式文件系统(HDFS)的经典架构中,NameNode作为核心组件承担着整个文件系统的元数据管理职责。这一设计虽然简洁高效,但随着数据规模的爆炸式增长,单NameNode架构逐渐暴露出难以克服…

为什么选择EasyGBS?

作为集 算法仓、算力设备接入、视频云平台 于一体的综合性智能安防监控平台,EasyGBS有哪些优势是您的必选理由呢?一、设备与协议的兼容性EasyGBS不挑设备品牌型号。只要支持GB28181、RTSP、ONVIF、RTMP标准协议里的任一种,就能将视频接入。但…

【形态学变换】——图像预处理(OpenCV)

目录 1 核 2 腐蚀 3 膨胀 4 开运算 5 闭运算 6 礼帽运算 7 黑帽运算 8 形态学梯度 形态学变换是一种基于形状的简单变换,处理对象是二值化后的图像。有两个输入:原图像和核,一个输出:形态学变换后的图像。基本操作有以下四…

一次“非法指令”(SIGILL)问题的完整调试过程:CPU指令集兼容性探秘

一次"非法指令"问题的完整调试过程:CPU指令集兼容性探秘一、问题概述二、问题现象与初步分析1. 环境与现象2. 官方文档的线索3. 重现问题4. 怀疑方向:CPU指令兼容性5. 关键发现:AVX512指令三、详细调试过程1. 搭建调试环境 (KVM虚拟…

Node.js - 创建 Express 项目

创建 Express 项目 安装 npm i -g express-generatorornpm i -g express-generator4# 注意:Windows有可能碰到提示:npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本。 # 如果碰到这个错误,需要…

高并发系统设计面试题

高并发系统设计面试题🔥🔥🔥 超高频问题(几乎必问)让你设计一个秒杀系统,你会考虑哪些问题?如果你的业务量突然提升100倍QPS你会怎么做?库存扣减如何避免超卖和少卖?订单…

【通识】如何看电路图

1. 电路图 1.1 基础概念 电路图即电原理图。 电路图第一种是说明模拟电子电路工作原理,用图形符号表示电阻器、电容器、开关、晶体管等实物,用线条把元器件和单元电路按工作原理的关系连接起来。 第二种则是说明数字电子电路工作原理的。用图形符号表示…

SpringBoot实战指南:从快速入门到生产级部署(2025最新版)

一、为什么SpringBoot依然是Java开发的首选? SpringBoot自2014年发布以来,已成为Java企业级开发的事实标准框架。根据2025年最新调研数据显示,全球78%的Java微服务项目基于SpringBoot构建,其核心优势在于: 约定优于配置…

新房装修是中央空调还是壁挂空调好?

这个要看户型和投资金额,大户型空间适合装中央空调,因为空间大有足够的地方安装,功率也可以根据面积大小进行配置,整体配置一个外机就行了,整体的装修效果比较规整,就是多花点,使用成本也稍高点…

如何理解泊松分布

文章目录一、引例——鲸鱼研究二、泊松分布一、引例——鲸鱼研究 有生态学家对生活在北冰洋水域的鲸鱼进行了跟踪研究,他们利用一台水下无人机来探测鲸鱼数量,这是近十天的数据: 第1天第2天第3天第4天第5天第6天第7天第8天第9天第10天10101…

python学习DAY22打卡

作业: 自行学习参考如何使用kaggle平台,写下使用注意点,并对下述比赛提交代码 kaggle泰坦尼克号人员生还预测 import warnings warnings.filterwarnings("ignore") #忽略警告信息 # 数据处理清洗包 import pandas as pd import …

在 Ansys CFX Pre 中配置 RGP 表的分步指南

掌握在 Ansys CFX Pre 中设置 RGP 表的技巧,以优化仿真精度和效率。挑战在计算流体动力学 (CFD) 领域,RGP(真实气体属性)表对于准确模拟流体在不同条件下的行为至关重要。这些表格提供了详细的热力学属性&a…

C语言————原码 补码 反码 (日渐清晰版)

本文的内容通下面这篇文章有着紧密的联系,读者可以选择性阅读 C语言————二、八、十、十六进制的相互转换-CSDN博客 目录 基本概念 原码 反码 补码 转换 数据的存储方式 基本存储单位 数据的计算方式 补码的模运算原理 移位操作符 左移操作符 右移操…

函数-变量的作用域和生命周期

变量的作用域 引入问题 我们在函数设计的过程中,经常要考虑对于参数的设计,换句话说,我们需要考虑函数需要几个参数,需要什么类型的参数,但我们并没有考虑函数是否需要提供参数,如果说函数可以访问到已定义…

Ansible在配置管理中的应用

Ansible是一个开源的配置管理和应用程序部署工具,它使用YAML语言编写的Playbook来描述配置和应用部署过程。通过SSH协议与目标机器通信,Ansible可以实现批量操作,极大地提升了工作效率。核心功能Ansible的核心功能包括:配置管理&a…

【学习路线】Go语言云原生开发之路:从简洁语法到微服务架构

一、Go语言基础入门(1-2个月) (一)环境搭建与工具链Go环境安装 官方安装:从golang.org下载安装包版本管理:g、gvm等Go版本管理工具环境变量:GOROOT、GOPATH、GOPROXY配置Go Modules:…

软件工厂:推动新质生产力的组织跃迁

引言:软件工厂的建设,不在于工具多,而在于理解深;不在于上线快,而在于体系稳。不仅是“看得见的流水线”,更是“看不见的组织变革”。在新质生产力的时代命题下,软件工厂正成为连接创新与效率、…

9.0% 年增速驱动!全球自清洁滚轮拖布机器人市场2031年将迈向 946 百万美元

自清洁滚轮拖布机器人是重要的智能清洁设备,采用滚筒式拖布结构,集扫拖功能,通过高速旋转加压擦洗地面,深度除污。其活水清洁系统可实时自清洁、回收污水,避免二次污染,提升清洁效率与效果,带来…

新能源工厂的可视化碳中和实验:碳足迹追踪看板与能源调度策略仿真

摘要新能源工厂明明用着风电、光伏等清洁能源,碳排放数据却依旧居高不下?某锂电池厂耗费百万升级设备,碳足迹却难以精准追踪,能源调度全靠经验“拍脑袋”,导致成本飙升。而隔壁企业通过可视化碳中和实验,碳…

数据结构自学Day13 -- 快速排序--“非递归利用栈实现”

一、快速排序回顾 快速排序本质上是**“分而治之”(Divide and Conquer)策略的递归应用。但递归其实就是函数栈的一种体现,因此我们也可以显式使用栈(stack)来模拟递归过程**,从而实现非递归版本的快速排序…