使用默认不可变的Rust变量会踩什么坑

讲动人的故事,写懂人的代码

Rust的变量真的是名不副实。名字中明明有个“变”字,却默认不可变。还美其名曰“不可变变量”。要想让变量名副其实,还必须费心额外加个mut关键字,并必须称其为“可变变量”,才能与前者区分开。这两个名字越琢磨越有趣。

与名不副实的变量相关的概念还真不少。

  • 声明、初始化和绑定变量的语句
  • 可用于变量赋值的一般表达式与控制流表达式
  • 变量的数据类型
  • 可以接受变量作为参数并能将返回值赋给变量的函数
  • 与变量一样都可以存储值的常量

变量名不副实这一点足以让程序员踩坑,而与变量相关的那些概念也暗藏着不少陷阱。那么,程序员在使用Rust的变量及其相关概念时,最容易在哪些场景中踩坑呢?

在这里插入图片描述

3.1 不可变变量绑定值后再为其赋值

对于Rust语言之外的其他编程语言来说,变量默认是可变的。这一点从变量的名字就显而易见。于是不少有其他语言使用背景的初学者,经常踩误为不可变变量赋值的坑。

3.1.1 在循环中误为不可变变量赋值

在循环中求和,是常见的计算方法。当如果忘记Rust的变量默认不可变,那么就会踩为不可变变量赋值的坑,如代码清单3-1所示。

为节省篇幅,本书大部分代码清单只展示最关键的代码(从不连续的行号能看出来)。完整代码可以在用git下载代码后,在代码清单开头注释中标明的源代码位置中查看。

本书代码下载链接为github.com/wubin28/book_LRBACP。本书所有的代码清单,会注明在这个链接中的文件夹位置,以便读者找到相应的没有行号的代码来运行。

下载代码之前,请先安装git。具体的安装步骤,可以询问你最喜欢用的生成式AI聊天工具。

之后,可以运行git clone命令,然后进入文件夹book_LRBACP即能看到所有代码。

代码清单3-1 在循环中误为不可变变量赋值

// 源代码位置:ch03/immutable_misstep3     let sum = 0;4     for i in 1..=3 {5         // sum += i;  // 取消注释这行以查看编译错误7     }

代码清单3-1所对应的完整源代码,展示了如何正确和错误地使用变量来计算1到3的累加和。代码通过三种不同的方式来阐述这个问题,突出了"不可变变量绑定值后再误为其赋值"的主旨。限于篇幅,书中只展示和解释重要的代码片段。对于完整源代码中不明白的语句,读者可以自行用最喜欢的生成式AI来解释。

第3行声明了一个不可变变量sum并初始化为0,也就是将0绑定到不可变变量sum上。这里是"误用不可变变量"问题的开始。

第4-7行使用for循环遍历1到3的范围。

第4行是Rust中的一个for循环语句。for 关键字表明要开始一个循环结构。i是循环变量。在每次迭代中,i 会被赋予范围中的下一个值。in这个关键字用来指定循环将遍历一个范围或集合。1..=3是一个范围表达式,它定义了循环将要遍历的值。.. 是Rust的范围语法。1..3 将创建一个不包含上界的范围,即 1 和 2。1..=3 中的 = 符号表示这是一个包含上界的范围。{这个大括号标志着循环体的开始。循环体中的代码将对范围中的每个值执行一次。所以,第4行完整含义是创建一个循环,其中变量 i 将依次取值 1、2 和 3。对于每个值,执行循环体中的代码。

第5行就踩坑里了。如果将第5行的注释去掉,那么这行代码就是其他主流编译语言通常的做法:用赋值的方法试图修改sum。但由于sum是不可变的,这会导致编译错误。

❗️变量避坑指南

不可变变量一旦绑定,就不能再赋值。

如何修复这个问题?代码清单3-1所对应的完整源代码展示了两种方法。一种是在第3行变量sum前,添加mut关键字,使其成为可变变量,这样把第5行的注释取消,编译就不再报错。另一种方法是使用函数式编程的方法,即只用let sum: i32 = (1..=3).sum();这一句,不仅能完成求和与sum的变量绑定工作,还不必把sum声明为mut。这样既省事,代码可读性也好了不少。

讲了半天变量,到底什么是Rust的变量?

❓什么是Rust的变量

Rust的变量是一个命名了的存储位置,它绑定了一个内存中的值,并遵循Rust的所有权规则和生存期规范。

具体来说,Rust的变量有一个标识符(名称),用于在代码中标识它。变量与一个特定的值相关联。这种关联在Rust中被称为"绑定"。变量代表了内存中存储的数据。每个值在任一时刻只能有一个所有者(即变量)。当变量离开作用域时,它所拥有的值会被自动清理。变量的生存期受到严格控制,确保在使用时始终有效。变量命名使用snake_case风格(即单词全小写,单词之间用下划线分隔)。作用域是变量在代码块中可以访问的范围,通常是从声明点开始到包含它的代码块结束,由大括号 {} 界定。

此外,Rust变量还有以下特征。

  • 默认不可变。除非明确声明为可变。不可变变量一旦被绑定就不能更改其值。
  • 类型安全。每个变量都有一个在编译时确定的类型,即使是通过类型推断确定的。
  • 作用域限制。变量的可见性和生存期通常限于声明它的代码块。
  • 支持遮蔽(详见3.3)。可以在同一作用域内多次声明同名变量,新变量会遮蔽旧变量(即旧变量失效)。

上面提到,代码清单3-1的第3行既有变量sum的声明,又有初始化,还提到了绑定。第5行还有赋值。那么变量的声明、初始化、绑定和赋值之间有什么联系和区别?

❓变量的声明、初始化、绑定与赋值

在Rust中,变量的声明、初始化、绑定与赋值是密切相关的概念,它们有一些细微的区别和特定的含义。

变量声明是在程序中引入一个新的变量名。在Rust中,变量声明通常使用 let 关键字。如下所示。

let x;  // 变量声明

变量初始化是给变量赋予一个初始值的过程。在Rust中,初始化通常在声明的同时完成。初始化标志着变量生存期的开始。变量的生存期,指变量从完成声明和初始化开始,到变量因所有权移动、被显式释放或离开作用域而结束的这段时间。

如下所示。

let x = 5;  // 变量声明并初始化,即创建一个绑定

❗️变量初始化避坑指南

变量只能被初始化一次。

**变量绑定结合了声明和初始化的概念。**在Rust中,变量"绑定"这个术语更为常用。当"绑定一个变量"时,通常指的是声明一个变量并将其与一个值关联起来。如上所示。上面这行代码将变量名 x 绑定到值 5 上。

在很多语言中,变量可以先声明后初始化。在Rust中,虽然可以将变量的声明和初始化分开(适用于变量在声明时无法立即确定其值,或变量的初始值需要通过某些计算或函数调用而得到的场景),但在使用变量之前,必须确保它已被初始化。Rust编译器会跟踪变量是否被初始化,以确保在使用前已经初始化。如下所示。

let x;      // 声明不可变变量x
x = 5;      // 初始化x,貌似为不可变变量赋值,但其实不是
println!("{}", x);  // 使用

❗️变量初始化避坑指南

当变量的声明和初始化分开时,初始化不要求变量是可变的。

**赋值是将一个新值存储到已经声明并初始化的可变变量中的过程。**可以多次进行赋值。赋值操作不会改变变量的类型。赋值可以发生在变量生存期内的任何时候。如下所示。

let mut x = 5;
x = 10; // 赋新值

❗️变量赋值避坑指南

只有可变变量才能被赋值。

在Rust中,绑定不仅仅是声明和初始化。它还涉及所有权(ownership)的概念。当绑定一个值到变量时,该变量成为这个值的唯一所有者。

Rust允许重新绑定同名变量,这被称为"遮蔽"(详见3.3)。

默认情况下,Rust中的绑定是不可变的。要创建可变绑定,需要使用 mut 关键字。如下所示。

let mut y = 5;  // 可变绑定
y = 6;          // 允许用赋值语句修改

Rust在绑定时可以进行类型推断,但也允许显式指定类型。如下所示。

let z = 5;       // 整型类型推断默认为 i32
let w: f64 = 5.0;  // 显式指定类型64位浮点数

在Rust中,绑定有明确的生存期,通常持续到变量离开作用域后结束。

变量绑定和赋值可能会涉及所有权的转移,特别是对于非复制(non-Copy)类型的值。

在这里插入图片描述

3.1.2 误为不可变结构体字段赋值

**结构体是Rust中用于创建自定义数据类型的一种方式。**它允许程序员将多个相关的值组合成一个有意义的组。当需要改结构体内某个字段的值的时候,会踩什么可变性的坑?代码清单3-2就是一个踩坑的例子。

代码清单3-2 误为不可变结构体字段赋值

// 源代码位置:ch03/immutable_field_mishap1 struct Point {2     x: i32,3     y: i32,4 }5 6 fn main() {8     let point = Point { x: 0, y: 0 };
10 
11     // point.x = 5;  // 取消注释这行以查看编译错误
42 }

代码清单3-2所对应的完整源代码,演示了三种情况:不可变结构体字段的赋值错误、使用可变结构体正确修改字段,以及使用RefCell实现内部可变性。代码的主旨是展示"误为不可变结构体字段赋值"的问题及其解决方法。

第1-4行定义了一个名为Point的结构体,包含两个i32类型的字段xy

第8行创建一个不可变的Point实例point,初始化xy坐标为0。这是踩坑的起点。

第11行踩坑了。这行被注释掉的代码试图用赋值,修改不可变结构体实例pointx坐标,如果取消注释,将导致编译错误。

如何修复这个问题?代码清单3-2所对应的完整源代码,给出了两种修复方法。

第一种方法是在第8行实例point前面,添加mut关键字,使其变为可变实例。

❗️结构体可变性避坑指南

默认情况下,结构体实例是不可变的。要创建可变的结构体实例,需要在声明结构体变量时使用 mut 关键字。结构体的可变性是整体的,不能只将某个字段标记为可变。

第二种方法是在保持point实例不可变的情况下,将其用智能指针RefCell<T>包裹起来。然后利用RefCell<T>的内部可变性,来改变不可变结构体实例point内部字段的值。

❗️在不可变上下文中改变数据的避坑指南

一个不可变变量所拥有的的数据,并不是完全不能修改。使用内部可变性,是能够实现在不可变上下文中改变数据的。内部可变性是 Rust 中的一种设计模式,它允许程序员在拥有不可变引用、不可变变量或不可变实例时改变数据。这看似违反了 Rust 的借用规则,但实际上并不是这样。内部可变性是在语言的安全保证内提供了一种受控的方式来实现可变性。RefCell<T>Cell<T>Mutex<T>RwLock<T>是实现内部可变性的常用智能指针类型。

如果喜欢这篇文章,别忘了给文章点个“赞”,好鼓励小吾继续写哦~😃

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

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

相关文章

使用kaggle命令下载数据集和模型

1、点击用户头像&#xff0c;点击Settings&#xff1a; 2、找到API&#xff0c;点击create new token&#xff0c;将自动下载kaggle.json&#xff1a; 3、在用户目录下创建.kaggle文件夹&#xff0c;并将下载的kaggle.json文件移动到该文件夹&#xff1a; cd ~ mv Downloads…

负载均衡--相关面试题(六)

在负载均衡的面试中&#xff0c;可能会遇到一系列涉及概念、原理、实践应用以及技术细节的问题。以下是一些常见的负载均衡面试题及其详细解答&#xff1a; 一、什么是负载均衡&#xff1f; 回答&#xff1a;负载均衡是一种将网络请求或数据传输工作分配给多个服务器或网络资源…

编码能力提升计划 - 华为OD统一考试(E卷)

2024华为OD机试(E卷+D卷+C卷)最新题库【超值优惠】Java/Python/C++合集 题目描述 为了提升软件编码能力,小王制定了刷题计划,他选了题库中的n道题,编号从0到n-1,并计划在m天内按照题目编号顺序刷完所有的题目(注意,小王不能用多天完成同一题)。 在小王刷题计划中,小王…

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-01

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-01 目录 文章目录 计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-01目录1. Beyond Text-to-Text: An Overview of Multimodal and Generative Artificial Intelligence for Education Using Topi…

QT-MySQL QSqlDatabase: QMYSQL driver not loaded

文章目录 问题解决操作&#xff1a;自己尝试编译&#xff0c;各种错误层出不穷&#xff1a; 解决问题检查总结&#xff1a; 问题 使用Qt连接mysql数据库&#xff0c;遇到了一个问题&#xff0c;就是QT5.14.1版本在连接MySQL数据库时候&#xff0c;提示驱动加载失败&#xff0c…

el-table添加fixed后错位问题

1 方案1 return {isShow:false, }mounted() {this.isShowtrue},watch: {$route(newRoute) {this.monitoredRoute newRoute; // 将新的路由信息保存到组件的monitoredRoute属性中// 执行其他操作或调用其他方法},//或$route(newRoute) {this.monitoredRoute newRoute; // 将新…

AI 对话工具汇总

&#x1f423;个人主页 可惜已不在 &#x1f424;这篇在这个专栏AI_可惜已不在的博客-CSDN博客 &#x1f425;有用的话就留下一个三连吧&#x1f63c; 目录 前言: 正文: 前言: 在科技飞速发展的时代&#xff0c;AI 对话正逐渐成为我们获取信息、交流思想的新方式。它以强…

【系统代码】招投标采购一体化管理系统,JAVA+vue

前言&#xff1a; 随着互联网和数字技术的不断发展&#xff0c;企业采购管理逐渐走向数字化和智能化。数字化采购平台作为企业采购管理的新模式&#xff0c;能够提高采购效率、降低采购成本、优化供应商合作效率&#xff0c;已成为企业实现效益提升的关键手段。系统获取在文末…

MES(软件)系统是什么?MES系统为何如此重要呢?

一、MES系统的定义与功能 MES系统是一套面向制造企业车间执行层的生产信息化管理系统&#xff0c;它涵盖了多种功能模块&#xff0c;包括但不限于&#xff1a; 订单管理&#xff1a;处理客户订单&#xff0c;确保生产需求与市场需求相匹配。生产调度&#xff1a;根据订单和生…

1.5 测试用例

欢迎大家订阅【软件测试】 专栏&#xff0c;开启你的软件测试学习之旅&#xff01; 文章目录 前言1 测试用例介绍2 测试用例编写3 案例分析4 执行测试用例 前言 测试用例的设计和编制是软件活动中最重要的工作。本文详细讲解了测试用例的基本概念以及如何编写测试用例。 本篇文…

React 解释常见的 hooks: useState / useRef / useContext / useReducer

前言 如果对 re-render 概念还不清楚&#xff0c;建议先看 React & 理解 re-render 的作用、概念&#xff0c;并提供详细的例子解释 再回头看本文。 如果对 React 基础语法还不熟练&#xff0c;建议先看 React & JSX 日常用法与基本原则 再回头看本文。 useState useS…

51单片机系列-串口(UART)通信技术

&#x1f308;个人主页&#xff1a; 羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 并行通信和串行通信 并行方式 并行方式&#xff1a;数据的各位用多条数据线同时发送或者同时接收 并行通信特点&#xff1a;传送速度快&#xff0c;但因需要多根传输线&#xf…

监控易监测对象及指标之:全面监控Sybase_New数据库

随着企业数据量的不断增长和业务的复杂化&#xff0c;数据库的稳定性和性能成为了保障业务连续性的关键因素。Sybase_New数据库作为众多企业选择的数据管理解决方案&#xff0c;其稳定性和性能对于企业的运营至关重要。 为了确保Sybase_New数据库的稳定运行和高效性能&#xff…

【Golang】深入解读Go语言中的错误(error)与异常(panic)

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

蓝桥杯—STM32G431RBT6(IIC通信--EEPROM(AT24C02)存储器进行通信)

一、什么是IIC&#xff1f;24C02存储器有什么用&#xff1f; IIC &#xff08;IIC 是半双工通信总线。半双工意味着数据在某一时刻只能沿一个方向传输&#xff0c;即发送数据的时候不能接收数据&#xff0c;接收数据的时候不能发送数据&#xff09;即集成电路总线&#xff08;…

AI智能时代的图书馆未来,你想象过吗!

AI智能时代的图书馆未来&#xff0c;你想象过吗&#xff01; 前言AI智能时代的图书馆未来 前言 教育数字化和 AI 时代的浪潮正汹涌而来&#xff0c;图书馆也站在了变革的十字路口。我们看到高等教育正在发生深刻的变革&#xff0c;从教学模式到人才培养理念&#xff0c;都在经…

Linux 再入门整理:详解 /etc/fstab 文件

目录 1. 什么是 /etc/fstab2. /etc/fstab 文件的格式2.1 设备文件 (Device)2.2 挂载点 (Mount Point)2.3 文件系统类型 (File System Type)2.4 挂载选项 (Mount Options)2.5 Backup Operation&#xff08;dump 参数&#xff09;2.6 Pass Order (fsck 参数)2.6.1 参数设置2.6.2 …

智慧防灾,科技先行:EasyCVR平台助力地质灾害视频监测系统建设

随着科技的飞速发展&#xff0c;视频监控技术已成为地质灾害监测与预警的重要手段之一。在众多视频监控平台中&#xff0c;EasyCVR视频汇聚平台凭借其强大的视频整合、实时传输、视频处理及分发等能力&#xff0c;在地质灾害场景中展现出显著的应用优势。 一、实时监测与远程监…

【RabbitMQ——具体使用场景】

1. 异步 1.1 同步异步的问题&#xff08;串行&#xff09; 串行方式&#xff1a;将订单信息写入数据库成功后&#xff0c;发送注册邮件&#xff0c;再发送注册短信。以上三个任务全部完成后&#xff0c;返回给客户端 public void makeOrder(){// 1 :保存订单 orderService.…

SpringBoot上传图片实现本地存储以及实现直接上传阿里云OSS

一、本地上传 概念&#xff1a;将前端上传的文件保存到自己的电脑 作用&#xff1a;前端上传的文件到后端&#xff0c;后端存储的是一个临时文件&#xff0c;方法执行完毕会消失&#xff0c;把临时文件存储到本地硬盘中。 1、导入文件上传的依赖 <dependency><grou…