Rust编程基础之引用与借用

news2024/11/26 10:32:08

1.引用与借用

在上一章节最后的代码中, 我们必须将 String 返回给调用函数,以便在调用 calculate_length 后仍能使用 String,因为 String 被移动到了 calculate_length 内。相反我们可以提供一个 String 值的引用(reference)。引用reference)像一个指针,因为它是一个地址,我们可以由此访问储存于该地址的属于其他变量的数据。 与指针不同,引用确保指向某个特定类型的有效值。

下面是如何定义并使用一个(新的)calculate_length 函数,它以一个对象的引用作为参数而不是获取值的所有权:

fn main() {
    let s1 = String::from("hello");

    let len = calculate_length(&s1);

    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

首先,注意变量声明和函数返回值中的所有元组代码都消失了。其次,注意我们传递 &s1calculate_length,同时在函数定义中,我们获取 &String 而不是 String。这些 & 符号就是 引用,它们允许你使用值但不获取其所有权。

下面是&String s指向String s1的示意图:

仔细看看这个函数调用:

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 并没有所有权。当函数使用引用而不是实际值作为参数,无需返回值来交还所有权,因为就不曾拥有所有权。

我们将创建一个引用的行为称为 借用borrowing)。正如现实生活中,如果一个人拥有某样东西,你可以从他那里借来。当你使用完毕,必须还回去。我们并不拥有它。

如果我们尝试修改借用的变量会发生什么? 看下面的代码:

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

编译这段代码, 编译器给了我们以下错误:

正如变量默认是不可变的,引用也一样。(默认)不允许修改引用的值。

2.可变引用

从上面的错误提示中, 编译器用绿色字体标出了我们应该怎么正确的定义引用变量, 在引用符号后面加上mut, 允许修改一个借用的值, 这就是可变引用。

看下面代码:

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

首先,我们必须将 s 改为 mut s。然后在调用 change 函数的地方创建一个可变引用 &mut s,并更新函数签名以接受一个可变引用 some_string: &mut String。这就非常清楚地表明,change 函数将改变它所借用的值。

可变引用有一个很大的限制:如果你有一个对该变量的可变引用,你就不能再创建对该变量的引用。这些尝试创建两个 s 的可变引用的代码会失败:

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

编译器报错如下:

这个报错说这段代码是无效的,因为我们不能在同一时间多次将 s 作为可变变量借用。第一个可变的借入在 r1 中,并且必须持续到在 println! 中使用它,但是在那个可变引用的创建和它的使用之间,我们又尝试在 r2 中创建另一个可变引用,该引用借用与 r1 相同的数据。

这一限制以一种非常小心谨慎的方式允许可变性,防止同一时间对同一数据存在多个可变引用。新手经常难以适应这一点,因为大部分语言中变量任何时候都是可变的。这个限制的好处是 Rust 可以在编译时就避免数据竞争。数据竞争data race)类似于竞态条件,它可由这三个行为造成:

  • 两个或更多指针同时访问同一数据。

  • 至少有一个指针被用来写入数据。

  • 没有同步数据访问的机制。

数据竞争会导致未定义行为,难以在运行时追踪,并且难以诊断和修复;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);

这段代码编译后错误如下:

是的, 我们 不能在拥有不可变引用的同时拥有可变引用。

不可变引用的用户可不希望在他们的眼皮底下值就被意外的改变了!然而,多个不可变引用是可以的,因为没有哪个只能读取数据的人有能力影响其他人读取到的数据。

注意一个引用的作用域从声明的地方开始一直持续到最后一次使用为止。例如,因为最后一次使用不可变引用(println!),发生在声明可变引用之前,所以如下代码是可以编译的:

let mut s = String::from("hello");
​
let r1 = &s; // 没问题
let r2 = &s; // 没问题
println!("{} and {}", r1, r2);
// 此位置之后 r1 和 r2 不再使用
​
let r3 = &mut s; // 没问题
println!("{}", r3);

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

尽管这些错误有时让人很烦,但请牢记这是 Rust 编译器在提前指出一个潜在的 bug(在编译时而不是在运行时)并精准显示问题所在。这样就不必去跟踪为何数据并不是想象中的那样。

3.悬垂引用

在具有指针的语言中,很容易通过释放内存时保留指向它的指针而错误地生成一个 悬垂指针,所谓悬垂指针是其指向的内存可能已经被分配给其它持有者。相比之下,在 Rust 中编译器确保引用永远也不会变成悬垂状态:当你拥有一些数据的引用,编译器确保数据不会在其引用之前离开作用域。

让我们尝试创建一个悬垂引用:

fn main() {
    let reference_to_nothing = dangle();
}
​
fn dangle() -> &String {
    let s = String::from("hello");
​
    &s
}

编译这段代码,编译器给出以下错误提示:

下面看看错误的代码部分发生了什么:

fn dangle() -> &String { // dangle 返回一个字符串的引用
​
    let s = String::from("hello"); // s 是一个新字符串
​
    &s // 返回字符串 s 的引用
} // 这里 s 离开作用域并被丢弃。其内存被释放。
  // 危险!

因为 s 是在 dangle 函数内创建的,当 dangle 的代码执行完毕后,s 将被释放。不过我们尝试返回它的引用。这意味着这个引用会指向一个无效的 String,这肯定是有问题的, Rust 不会允许这么做。

解决方案是直接返回String,代码如下:

fn no_dangle() -> String {
    let s = String::from("hello");
​
    s
}

这样就没有任何错误了。所有权被移动出去,所以没有值被释放。

4.总结

上面针对引用的讨论,总结如下:

  • 在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用。

  • 引用必须总是有效的。

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

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

相关文章

BGF-YOLO | 增强版YOLOV8 | 用于脑瘤检测的多尺度注意力特征融合

基于You Only Look Once(YOLO)的目标检测器在自动脑瘤检测中展现出卓越的准确性。在本文中,我们开发了一种新的BGF-YOLO架构,通过将双层路由注意力(BRA)、广义特征金字塔网络(GFPN)和第四检测头整合到YOLOv8中来实现。BGF-YOLO包含了一个注意力机制,用于更加关注重要的…

北马“破3收割机”,特步成赛场和市场双面“赢家”

北马已经落下帷幕,尽管当天全国共有20多场城市马拉松开跑,但作为国内第一个城市马拉松,有“国马”之称的北马,还是赛事的中心点。北马相关的所有标签,都会在跑圈内外引起足够多的讨论,比如一双跑鞋——顶级…

局域网和广域网的区别

局域网靠交换机通信; 广域网靠路由器将多个局域网连接起来通信; 在防火墙内部的叫内网,外部的叫外网; 用ipconfig查到的ip是本机的内网IP;在网页上看到的是连接互联网所用的IP即往往IP 记住:IP地址是唯一的&#x…

阿里云服务器省钱购买和使用方法(图文详解)

阿里云服务器使用教程包括云服务器购买、云服务器配置选择、云服务器开通端口号、搭建网站所需Web环境、安装网站程序、域名解析到云服务器公网IP地址,最后网站上线全流程,新手站长xinshouzhanzhang.com分享阿里云服务器详细使用教程: 一&am…

访问控制、RBAC和ABAC模型

访问控制、RBAC和ABAC模型 访问控制 访问控制的目的是保护对象(数据、服务、可执行应用该程序、网络设备或其他类型的信息技术)不受未经授权的操作的影响。操作包括:发现、读取、创建、编辑、删除和执行等。 为实现访问控制, 计…

第二阶段第二章——数据库SQL

到这里就是属于原有的知识的扩充了。、 发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发疯发…

CentOS 7 安装 JDK11(注意版本号要与自己的版本一致)

查看是否有自带的 JDK java -versionrpm -qa | grep jdk卸载自带 JDK rpm -e --nodeps [name] # 如 rpm -e --nodeps java-1.8.0-openjdk-1.8.0.242.b08-1.el7.x86_64查看自带 JDK 是否卸载干净 java -versionrpm -qa | grep jdk在 oracle 官网下载自己所需 JDK 版本&#x…

JavaEE平台技术——MyBatis

JavaEE平台技术——MyBatis 1. 对象关系映射框架——Hibernate、MyBatis2. 对象关系模型映射3. MyBatis的实现机制4. MyBatis的XML定义5. Spring事务 在观看这个之前,大家请查阅前序内容。 😀JavaEE的渊源 😀😀JavaEE平台技术——…

Mythical Beings里的DAO治理指南——Mythical DAO使用方法浅析

DAO(Decentralized Autonomous Organization)去中心化自治组织,正在成为Web3世界的重要组织和治理形式。通过区块链和加密通证等技术实现transparent、inclusive的社区自治。Mythical Beings游戏也推出了Mythical DAO,帮助玩家社区自治。本文主要介绍当前热门的DAO治理工具,以及…

行政处罚有哪些?

一、行政处罚的定义 行政处罚,是指行政机关依法对违反行政管理秩序的公民、法人或者其他组织,以减损权益或者增加义务的方式予以惩戒的行为。这是首次以立法的形式明确“行政处罚”的定义。 二、行政处罚与行政处分的区别 行政处分是指国家行政机关对其…

分布式系统之BASE理论

BASE理论是对分布式系统设计和处理的一种理论指导,相对于ACID(原子性、一致性、隔离性和持久性)这一强一致性模型,BASE更强调在分布式系统中牺牲强一致性以获得可用性和性能的平衡。 BASE理论的核心概念包括: Basica…

用了这款工具,让我效率提升了80%

目录 一、写在前面 二、优势 三、主要功能点 四、低代码归根结底差不多 五、小结 一、写在前面 低代码开发平台,一个号称能在几分钟的时间里开发出一套公司内部都可使用的应用系统开发工具。很多人或许都隐隐听说过低代码,因为低代码不仅远名国外&#x…

FastGPT | 3分钟构建属于自己的AI智能助手

这是一篇使用指南!!! FastGPT是什么? FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开箱即用的数据处理、模型调用等能力。同时可以通过 Flow 可视化进行工作流编排,从而实现复杂的问答场景&…

DASCTF X CBCTF 2023|无畏者先行

前言 笔者没有参加此次比赛,由于团队后面会复现此次比赛,所以笔者在此进行复现记录。 EASYBOX 考点:命令执行? 栈溢出 附件给了 docker 环境,可以直接在本地复现,但是 docker 我不会调试,幸…

视频号提取视频工具,使用教程合集

第一步: 点击关注公众号下方菜单,视频下载-【视频号提取视频工具】,长按识别二维码,添加视频下载小助手为好友。 没有关注的可以,点击下载关注一下 第二步: 选择你要下载的视频,找到转发图标…

AD教程 (八)器件的复制和对齐

AD教程 (八)器件的复制和对齐 设置原理图工作区域 设置Sheet Size,将A4改为A3,增大原理图区域 按照原理图布局大致排布元件 框选需要复制的元件,按住Shift即可复制元件并拖动到合适的位置。如果元件需要对齐&#xf…

JUC-3-并发锁

一 JAVA 多线程锁介绍 1 悲观锁 定义:悲观锁指对数据被外界修改持保守态度,认为数据很容易就会被其他线程修改(很悲观),所以在数据被处理前先对数据进行加锁,并在整个数据处理过程中,使数据处…

seata事务回滚引起的skywalking数据库存储空间剧增的问题排查

基本信息 产品名称:ATS3.0 问题分类:编码问题 环境类型:环境无关 问题现象 11月1日上午华润DBA收到数据库磁盘空间告警,检查后发现skywalking连接的mysql数据库占用空间从之前一直是比较稳定的,但是10月31日…

ElasticSearch与Lucene是什么关系?Lucene又是什么?

一. ElasticSearch 与 Lucene 的关系 Elasticsearch(ES)和Apache Lucene之间有密切的关系,可以总结如下: Elasticsearch构建于Lucene之上:Elasticsearch实际上是一个分布式的、实时的搜索和分析引擎,它构建…

二维码智慧门牌管理系统全新升级:个性化配置,智能管理,让你的社区更安全!

文章目录 前言一、个性化配置功能的升级二、智能化管理和便捷性 前言 随着科技的飞速发展,智能化管理已经成为各个领域的标配。在社区管理方面,智能化的优势在便捷性、高效性和安全性方面得到了广泛认可。最近,二维码智慧门牌管理系统经过全…