【Rust自学】4.4. 引用与借用

news2024/12/24 20:23:09

4.4.0 写在正文之前

这一节的内容其实就相当于C++的智能指针移动语义在编译器层面做了一些约束。Rust中引用的写法通过编译器的约束写成了C++中最理想、最规范的指针写法。所以学过C++的人对这一章肯定会非常熟悉。

喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)

4.4.1. 引用

引用让函数使用某个值而不获得其所有权,声明时在类型前加上&即代表引用。例如String的引用就是&String。如果学过C++的话,C++中的解引用符号是*,Rust中也是一样的。

学了引用之后,就可以把上一篇文章最后的示例代码给简化

这是先前的代码:

fn main(){
	let s1 = String::from("hello");
	let (s2, len) = calculate_length(s1);
	println!("The length of '{}' is {}", s2, len);
}

fn calculate_length(s:String) -> (String, uszie) {
	let length = s.len();
	(s, length)
}

这是修改后的代码:

fn main(){
	let s1 = String::from("hello");
	let length = calculate_length(&s1);
	println!("The length of '{}' is {}", s1, length);
}

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

对比两者,后者中数据的指针被传入函数calculate_length供其操作,而数据所有权依然在变量s1上。不需要返回元组,也不需要再声明一个变量s2,更加简洁。

函数calculate_length的参数s实际上是一个指针,指向s所在栈内存位置(不会直接指向堆内存中的数据)。这个指针在走出作用域时,Rust并不会消除其指向的数据(因为s没有所有权),只会弹出栈上所存储的指针信息,也就是释放下图中的最左侧的部分所占的内存。
请添加图片描述

这种以引用作为函数的参数叫做借用

4.4.2. 借用的特性

借用的内容是不能被修改的,除非是可变引用

以房产为例:你把自己有房产权的房子租给别人就是借用,租户只能住不能乱装修,这就是借用的内容不能被修改的特性;如果你允许租客装修,这就是可变引用。

以这个代码为例:

fn main(){
	let s1 = String::from("hello");
	let length = calculate_length(&s1);
	println!("The length of '{}' is {}", s1, length);
}

fn calculate_length(s:&String) -> usize {
	s.push_str(", world");
	s.len()
}

在编译时这个代码会报错:

error[E0596]: cannot borrow `*s` as mutable, as it is behind a `&` reference

报错的原因在于s.push_str(", world");这一行:引用默认是不可变的,但这一行修改了其数据内容。

引用跟普通的变量声明一样,默认不可变,但加上mut关键字后就可变了:

fn main(){
	let mut s1 = String::from("hello");
	let length = calculate_length(&mut s1);
	println!("The length of '{}' is {}", s1, length);
}

fn calculate_length(s:&mut String) -> usize {
	s.push_str(", world");
	s.len()
}

这样写就不会报错了(但记得在声明s1时把s1声明为可变变量)

这种可以修改数据内容的引用就叫做可变引用

4.4.3. 可变引用的限制

可变引用有两个非常重要的限制,其一是:在特定作用域内,对某一块数据,只能有一个可变的引用。

以这个代码为例:

fn main() {
	let mut s = String::from("hello");
	let s1 = &mut s;
	let s2 = &mut s;
}

因为s1s2都是指向s的可变引用,且在同一个作用域内,所以在编译时会报错:

error[E0499]: cannot borrow `s` as mutable more than once at a time

这么做的目的是防止数据竞争,以下三种条件同时满足时会发生数据竞争:

  • 两个或多个指针同时访问同一个数据
  • 至少有一个指针用于写入数据
  • 没有使用任何机制来同步对数据的访问

在报错信息中提及了at a time,意思为同时(也就是在同一个作用域内)。所以说,只要不同时,也就是两个可变引用在不同的作用域指向同一块数据是可以的。下面的代码就体现了这一点:

fn main() {
	let mut s = String::from("hello");
	{
		let s1 = &mut s;
	}
	let s2 = &mut s;
}

s1s2作用域不相同,所以指向同一块数据是允许的。

可变引用的第二个重要限制是:不可以同时拥有一个可变引用和一个不变的引用 因为可变引用存在的目的是修改数据内容,不变的引用存在的作用就是为了保持数据内容不变,如果两者同时存在,可变引用修改值之后,不可变引用的作用就失效了。

fn main() {
	let mut s = String::from("hello");
	let s1 = &mut s;
	let s2 = &s;
}

因为s1是可变引用,s2是不可变引用,两者出现在同一个作用域指向同一块数据,所以编译器会报错:

error[E0502]: cannot borrow `s` as mutable because it also borrowed as immutable

当然,多个不可变的引用是可以同时出现的

总结:多个读(不可变引用)是可以同时存在的,多个写(可变引用)可以存在但不能同时,多个写和同时读写是不允许的。

4.4.4. 悬空引用(Dangling References)

在使用指针时非常容易引起叫做悬空指针(Dangling Pointer) 的错误,其定义为:一个指针引用了内存中的某个地址,而这块内存可能已经释放并分配给其他人使用了。

如果你引用了某些数据,Rust编译器保证在引用离开作用域前数据不会离开作用域。 这是Rust保证悬空引用永远不会出现的做法。

以这个代码为例:

fn main() {
	let r = dangle();
}

fn dangle() -> &String {
	let s = String::from("hello");
	&s
}
  • 创建了一个局部变量 s:
    变量s是一个String,它被分配在栈上,但其底层数据存储在堆上。
  • 返回对s的引用:
    函数最后通过&s返回了s的引用。
  • s 的作用域结束:
    在函数dangle返回后,变量s离开了作用域,根据Rust所有权规则,s的内存被自动释放,&s所指向的内存数据已不再存储s的数据,返回的引用指向的是已经被释放的内存地址,变成了悬空引用(Dangling Pointer)。

Rust的编译器会检查到这一点,在编译时会报错。

4.4.5. 引用的规则

  • 在任何给定的时刻,只能满足下列条件之一:
    • 一个可变的引用
    • 任意数量不可变的引用
  • 引用必须一直有效

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

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

相关文章

电脑使用CDR时弹出错误“计算机丢失mfc140u.dll”是什么原因?“计算机丢失mfc140u.dll”要怎么解决?

电脑使用CDR时弹出“计算机丢失mfc140u.dll”错误:原因与解决方案 在日常电脑使用中,我们时常会遇到各种系统报错和文件丢失问题。特别是当我们使用某些特定软件,如CorelDRAW(简称CDR)时,可能会遇到“计算…

C# 基本信息介绍

总目录 前言 对 C# 做一个基本信息介绍,让我们对 C# 有个基本的认识。 在进行本文的阅读之前,可以瞧瞧 编程基础知识简述 简单的入个门儿。 一、C# 1. C# 概述 C#是由微软公司发布的一种由C和C衍生出来的面向对象的编程语言。 2. C# 详细介绍 C#&am…

『Linux学习笔记』FRPC 详细介绍及配置解析!

『Linux学习笔记』FRPC 详细介绍及配置解析! 文章目录 一. FRPC 详细介绍及配置解析FRPC 的主要功能FRPC 配置文件解析全局配置代理配置第一个代理服务第二个代理服务 配置文件整体工作流程常见配置项说明FRPC 的使用步骤注意事项结论 二. 参考文献 一. FRPC 详细介…

WPS工具栏灰色怎么办

WPS离线不登录,开启工具栏等相关功能 当你在使用WPS的过程中,若因网络问题或其他特殊原因,导致无法登录使用WPS时,可根据以下步骤开启离线兼容模式,开启此模式后,可在未登录的状态下,激活并使用…

【微信小程序】2|轮播图 | 我的咖啡店-综合实训

轮播图 引言 在微信小程序中,轮播图是一种常见的用户界面元素,用于展示广告、产品图片等。本文将通过“我的咖啡店”小程序的轮播图实现,详细介绍如何在微信小程序中创建和管理轮播图。 轮播图数据准备 首先,在home.js文件中&a…

JavaEE进阶--mybatis使用测试日志参数传递浏览器访问

文章目录 1.项目创建2.mybatis的使用2.1创建初始页面2.2补充yml文件2.3navicate表2.4用户类的编写2.5查询接口2.6运行测试 3.细节说明3.1java开发规范3.2关于包3.3持久层代码 4.测试文件4.1如何生成4.2生成位置4.3补充方法 5.配置mybatis日志6.参数传递6.1单个参数6.2多个参数 …

IDEA用jformdesigner插件做管理系统MVC架构

在 IntelliJ IDEA 中结合 JFormDesigner 插件,通过 Swing 框架实现一个管理系统的 MVC 架构是一种经典的开发方式。以下是具体的步骤和实现思路,包含从项目创建到 MVC 架构的核心代码实现。 1. 项目结构设计 为了清晰的 MVC 分层架构,建议按…

学习Cookie 提升

目录 Cookie 的覆盖​​​​​​​ Cookie下的path 特点 设置Cookie 路径 实例 Cookie的最大存活时间 设置Cookie 存活时间 实例 Cookie 和session的区别 和联系 Cookie 的覆盖 当 key相同 和只要path的上级目录的路径相同,就可以被替换掉 value 值 如下图…

021、深入解析前端请求拦截器

目录 深入解析前端请求拦截器: 1. 引言 2. 核心实现与基础概念 2.1 基础拦截器实现 2.2 响应拦截器配置 3. 实际应用场景 3.1 完整的用户认证系统 3.2 文件上传系统 3.3 API请求缓存系统 3.4 请求重试机制 3.5 国际化处理 4. 性能优化实践 4.1 请求合并…

4、mysql高阶语句

mysql高阶语句是对复杂的条件进行查询的操作。 排序—order by 加了desc表示由大到小 1、查询name和score,地址都是云南西路的按id进行由小到大排序 2、查询name和score,先按hobbid进行排序,再把结果按id进行排序 第一段字段必须要有相同的…

叉车作业如何确认安全距离——UWB测距防撞系统的应用

叉车在工业环境中运行,常常需要在狭窄的空间内完成货物的搬运和堆垛,这对操作员的技术水平和安全意识提出了极高的要求。传统的叉车作业依赖操作员的经验和视觉判断来确认安全距离,然而这种方式往往存在误差,特别是在视线受阻或光…

LLaVA 多模态大模型:两阶段训练,实现视觉与语言模态的高效融合

LLaVA 多模态大模型:两阶段训练,实现视觉与语言模态的高效融合 论文大纲理解确认目标分析过程实现步骤效果展示金手指 结构分析1. 层级结构分析叠加形态(从基础到高级)构成形态(部分到整体)分化形态&#x…

PostgreSQL 的历史

title: PostgreSQL 的历史 date: 2024/12/23 updated: 2024/12/23 author: cmdragon excerpt: PostgreSQL 是一款功能强大且广泛使用的开源关系型数据库管理系统。其历史可以追溯到1986年,当时由加州大学伯克利分校的一个研究团队开发。文章将深入探讨 PostgreSQL 的起源、…

台球助教平台系统开发APP和小程序信息收藏功能需求解析(第十二章)

以下是开发台球助教系统客户端(APP,小程序,H5)几端的信息收藏功能的详细需求和功能说明,内容比较详细,可以说是一个教科书式的详细说明了,这套需求说明不仅仅用在我们的台球助教系统程序上&…

SRE 与 DevOps记录

flashcat https://flashcat.cloud

Linux Shell 脚本编程基础知识篇—shell 运算命令详解

ℹ️大家好,我是练小杰,本文继续Linux shell脚本编程的基础知识内容,接着讲算术运算命令的详细操作~~ 复习:【shell简介以及基本操作】 更多Linux 相关内容请点击👉“Linux专栏”~ 文章目录 let运算命令的用法let 的高…

2002 - Can‘t connect to server on ‘192.168.1.XX‘ (36)

参考:2002 - Can‘t connect to server on ‘192.168.1.XX‘ (36) ubantu20.04,mysql5.7.13 navicat 远程连接数据库报错 2002 - Can’t connect to server on ‘192.168.1.61’ (36) 一、查看数据库服务是否有启动,发现有启动 systemctl status mysql…

GitCode 光引计划投稿|MilvusPlus:开启向量数据库新篇章

在人工智能和大数据时代,向量数据库作为处理非结构化数据的核心技术,正变得越来越重要。MilvusPlus,作为「光引计划」的一部分,应运而生,旨在提供一个高性能、易扩展、全功能的向量数据库解决方案。项目背景根植于对现…

一起学Git【第四节:添加和提交文件】

通过前三节的学习,基本上对Git有了初步的了解,下面开始进行文件的添加和提交的流程。 这里主要涉及四个命令: git init 创建仓库git status查看仓库状态git add添加至暂存区git commit提交文件之前已经使用过git init命令了,此处不再具体讲解。参照一起学Git【第二节:创建…

RISC-V架构的压缩指令集介绍

1、压缩指令集介绍 RISC-V的压缩指令集(C扩展)‌是一种设计用于减少代码大小和提高性能的技术。标准的RISC-V指令是32位,压缩指令集可以将部分32位的指令用16位的指令替代,从未减小程序占用存储空间的大小,提高指令密…