Rust 变量与可变性

article/2025/7/6 9:48:15

文章目录

  • 变量与可变性
    • 常量
    • 遮蔽(Shadowing)

变量与可变性

Rust 变量与可变性

Rust中变量默认是不可变的,这是 Rust 鼓励你编写更安全、易于并发代码的众多方式之一。不过,你仍然可以选择让变量可变。让我们来探讨 Rust 为什么鼓励你优先使用不可变性,以及为什么有时你可能需要选择可变性。

当变量是不可变的时,一旦一个值被绑定到名字上,你就无法更改该值。为说明这一点,请在你的项目目录下用 cargo new variables 创建一个新项目。

然后,在新建的 variables 目录下,打开 src/main.rs,并将其代码替换为以下内容(此代码暂时无法编译):

文件名:src/main.rs

此代码编译不通过!

fn main() {let x = 5;println!("The value of x is: {x}");x = 6;println!("The value of x is: {x}");
}

保存并使用 cargo run 运行程序。你会收到关于不可变性错误的提示,如下所示:

$ cargo runCompiling variables v0.1.0 (file:///projects/variables)
error[E0384]: cannot assign twice to immutable variable `x`--> src/main.rs:4:5|
2 |     let x = 5;|         - first assignment to `x`
3 |     println!("The value of x is: {x}");
4 |     x = 6;|     ^^^^^ cannot assign twice to immutable variable|
help: consider making this binding mutable|
2 |     let mut x = 5;|         +++

有关此错误的更多信息,请尝试 rustc --explain E0384
error: could not compile variables (bin “variables”) due to 1 previous error

这个例子展示了编译器如何帮助你发现程序中的错误。

你收到“不能对不可变变量 x 进行二次赋值”的错误,是因为你试图给不可变变量 x 赋第二个值。

当我们试图更改被指定为不可变的值时,在编译时报错是很有价值的,因为不应改变值的变量发生了改变很容易导致 bug。如果代码的一部分要求某个值永远不变,而另一部分代码却改变了这个值,那么第一部分代码可能就无法按预期工作。尤其是当第二部分代码只在某些情况下才改变值时,这种 bug 很难追踪。Rust 编译器保证你声明值不会改变时,它真的不会改变,因此你无需自己跟踪。这样你的代码更易于理解。

但可变性有时非常有用,也能让代码更方便编写。虽然变量默认是不可变的,但我们可以在变量名前加上 mut 使其可变。加上 mut 也向未来的代码阅读者传达了意图,表明代码的其他部分会更改该变量的值。

例如,让我们将 src/main.rs 改为如下内容:

文件名:src/main.rs

fn main() {let mut x = 5;println!("The value of x is: {x}");x = 6;println!("The value of x is: {x}");
}

现在运行程序,输出如下:

$ cargo runCompiling variables v0.1.0 (file:///projects/variables)Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30sRunning `target/debug/variables`
The value of x is: 5
The value of x is: 6

使用 mut 后,我们可以将 x 的值从 5 改为 6。最终,是否使用可变性取决于我们实际的需求和应用场景。

常量

与不可变变量类似,常量也是绑定到名字上的值,且不允许更改,但常量和变量之间有一些区别。

首先,常量不能使用 mut。常量不仅默认不可变——它们始终不可变。我们需要用 const 关键字声明常量,而不是 let,并且必须标注值的类型。

常量可以在任何作用域声明,包括全局作用域,从而可供多个模块或代码片使用。

最后一个区别是,常量只能被赋值为常量表达式,而非在运行时计算的值。

下面是一个常量声明的例子:

const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;

常量名为 THREE_HOURS_IN_SECONDS,其值为 60(每分钟的秒数)乘以 60(每小时的分钟数)再乘以 3(我们想要计算的小时数)。Rust 的常量命名约定是全大写并用下划线分隔单词。编译器能在编译时完成有限的运算,这让我们可以用更易理解的方式写出这个值,而不是直接写 10,800。更多关于常量声明时可用操作的信息,请参阅 Rust Reference 的常量求值部分。

在声明的作用域内,常量于整个程序运行期间都有效。此属性使得常量可以作为多处代码使用的全局范围的值,例如一个游戏中所有玩家可以获取的最高分或者光速。

将程序中用到的硬编码值声明为常量,能帮助后来的代码维护人员了解值的意图。如果将来需要更改硬编码值,也只需改动一处即可。

遮蔽(Shadowing)

正如你在第 2 章的猜数字游戏教程中看到的,你可以用相同的名字声明新变量。Rustacean 称第一个变量被第二个变量“遮蔽”,即编译器在使用变量名时会看到第二个变量。实际上,第二个变量覆盖了第一个变量,直到它自己被遮蔽或作用域结束。我们可以通过重复使用 let 关键字和相同变量名来遮蔽变量,如下所示:

文件名:src/main.rs

fn main() {let x = 5;let x = x + 1;{let x = x * 2;println!("The value of x in the inner scope is: {x}");}println!("The value of x is: {x}");
}

该程序首先将 x 绑定为 5。然后通过 let x = x + 1; 创建了一个新变量 x,此时 x 的值为 6。接着,在用花括号创建的内部作用域中,第三个 let 语句再次遮蔽 x,创建了一个新变量,其值为前一个值乘以 2,即 12。当该作用域结束后,内部遮蔽结束,x 恢复为 6。运行该程序,输出如下:

$ cargo runCompiling variables v0.1.0 (file:///projects/variables)Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31sRunning `target/debug/variables`
The value of x in the inner scope is: 12
The value of x is: 6

遮蔽与将变量标记为 mut 不同,因为如果你试图在没有使用 let 关键字的情况下重新赋值,会得到编译时错误。通过使用 let,我们可以对值进行多次转换,但在这些转换完成后变量仍然是不可变的。

mut遮蔽的另一个区别是,使用 let 关键字实际上创建了一个新变量,因此我们可以更改值的类型,但仍然复用相同的名字。例如,假设我们的程序让用户输入文本之间要有多少个空格,用户输入空格字符后,我们想把这个输入存储为数字:

let spaces = "   ";
let spaces = spaces.len();

第一个 spaces 变量是字符串类型,第二个 spaces 变量是数字类型。遮蔽让我们无需起不同的名字(如 spaces_str 和 spaces_num),而可以复用更简单的 spaces 名字。但如果我们尝试用 mut,如下所示,会得到编译时错误:

此代码无法编译!

let mut spaces = "   ";
spaces = spaces.len();

错误提示我们不能更改变量的类型:

$ cargo runCompiling variables v0.1.0 (file:///projects/variables)
error[E0308]: mismatched types--> src/main.rs:3:14|
2 |     let mut spaces = "   ";|                      ----- expected due to this value
3 |     spaces = spaces.len();|              ^^^^^^^^^^^^ expected `&str`, found `usize`

有关此错误的更多信息,请尝试 rustc --explain E0308
error: could not compile variables (bin “variables”) due to 1 previous error

整体代码示例如下:

fn main() {// 变量可变性  let mut x = 5;println!("The value of x is: {x}");x = 6;println!("The value of x is: {x}");// 常量const MAX_POINTS: u32 = 100_000;println!("The maximum points is: {MAX_POINTS}");// 变量遮蔽let x = 5;let x = x + 1;{let x = x * 2;println!("The value of x in the inner scope is: {x}");}println!("The value of x in the outer scope is: {x}");
}

运行结果如下

The value of x is: 5
The value of x is: 6
The maximum points is: 100000
The value of x in the inner scope is: 12
The value of x in the outer scope is: 6

现在我们已经了解了变量的工作方式,接下来让我们看看它们可以拥有的数据类型。


http://www.hkcw.cn/article/BtFJZIiiuM.shtml

相关文章

YOLOV7改进之融合深浅下采样模块(DSD Module)和轻量特征融合模块(LFI Module)

目录 一、研究背景​ 二. 核心创新点​ ​2.1 避免高MAC操作​ ​2.2 DSDM-LFIM主干网络​ 2.3 P2小目标检测分支​ ​3. 代码复现指南​ 环境配置 关键修改点 ​4. 实验结果对比​ 4.1 VisDrone数据集性能 4.2 边缘设备部署 4.3 检测效果可视化 ​5. 应用场景​ …

(Python网络爬虫);抓取B站404页面小漫画

目录 一. 分析网页 二. 准备工作 三. 实现爬虫 1. 抓取工作 2. 分析工作 3. 拼接主函数&运行结果 四. 完整代码清单 1.多线程版本spider.py: 2.异步版本async_spider.py: 经常逛B站的同志们可能知道,B站的404页面做得别具匠心&…

Qt OpenGL 3D 编程入门

Qt 提供了强大的 OpenGL 集成功能,使得在 Qt 应用中实现 3D 图形变得更加简单。以下是使用 Qt 进行 OpenGL 3D 编程的基础知识。 1. 环境配置 创建 Qt 项目 新建 Qt Widgets Application 项目 在 .pro 文件中添加 OpenGL 模块: qmake QT co…

山东警方:10岁失联男童在河道溺亡 搜救行动告终

6月2日,山东滕州市公安局发布警情通报。5月31日22时35分许,警方接到孔某某报警,称其10岁的外孙赵某某于当天17时许离家后失联。接警后,公安机关迅速调阅监控、走访群众,并联合当地政府和社会救援力量,使用搜救警犬和无人机持续开展搜寻工作。6月2日15时许,在邻村一河道内…

韩国大选目前选情如何 李在明领跑选战

韩国第21届总统选举定于6月3日举行,主要候选人仍在抓紧最后机会展开竞选活动,争取更多选票。此次大选在前总统尹锡悦被弹劾后举行,共有7名候选人登记参选,但其中两人已宣布退选,最终有5名候选人参加角逐。这5名候选人分别是共同民主党候选人李在明、国民力量党候选人金文洙…

【深度学习新浪潮】以Dify为例的大模型平台的对比分析

我们从核心功能、适用群体、易用性、可扩展性和安全性五个维度展开对比分析: 一、核心功能对比 平台核心功能多模型支持插件与工具链Dify低代码开发、RAG增强、Agent自律执行、企业级安全支持GPT-4/5、Claude、Llama3、Gemini及开源模型(如Qwen-VL-72B),支持混合模型组合可…

【JAVA】注解+元注解+自定义注解(万字详解)

📚博客主页:代码探秘者 ✨专栏:《JavaSe》 其他更新ing… ❤️感谢大家点赞👍🏻收藏⭐评论✍🏻,您的三连就是我持续更新的动力❤️ 🙏作者水平有限,欢迎各位大佬指点&…

unity随机生成未知符号教程

目录 前言方法1方法2脚本后言示例代码 前言 在某些游戏中,有一些让人感到意味不明的未知符号,例如在游戏《巴别塔圣歌》中,就有这样一些能让人在初次就看不懂的未知符号。 或者在其他时候,这些未知符号如果跟粒子系统结合在一起的…

OpenCV——Mac系统搭建OpenCV的Java环境

这里写目录标题 一、源码编译安装1.1、下载源码包1.2、cmake安装1.3、java配置1.4、测试 二、Maven引入2.1、添加Maven依赖2.2、加载本地库 一、源码编译安装 1.1、下载源码包 官网下载opencv包:https://opencv.org/releases/ 以4.6.0为例,下载解压后&…

从Docker拉取镜像一直失败超时解决办法

项目场景: 在ubuntu中,使用docker拉去镜像时,一直超时,拉去失败。 问题描述 原因分析: 国外服务器网络不好导致。 解决方案: 解决方案1 设置国内源 我这边测试,更改以后仍然失败 阿里云提供…

告别printf!嵌入式系统高效日志记录方案

目录 1、分级控制与动态过滤机制 2、异步处理与零拷贝架构 3、跨平台适配层设计 在嵌入式系统开发领域,日志记录系统如同数字世界的黑匣子,承载着系统运行状态的关键信息。传统的printf调试方式虽简单易用,但在处理复杂系统时暴露出效率低…

复变函数 $w = z^2$ 的映射图像演示

复变函数 w z 2 w z^2 wz2 的映射图像演示 复变函数 w z 2 w z^2 wz2 是一个基本的二次函数,在复平面上具有有趣的映射性质。下面我将介绍这个函数的映射特性,并使用MATLAB进行可视化演示。 映射特性 极坐标表示:若 z r e i θ z …

【Redis】Zset 有序集合

文章目录 常用命令zaddzcardzcountzrange && zrevrangezrangebyscorezpopmax && bzpopmaxzpopmin && zpopmaxzrank && zrevrankzscorezremzremrangebyrankzremrangebyscorezincrby 集合间操作交集 zinterstore并集 zunionstore 内部编码应用场…

【AI论文】视觉语言模型中的自我修正推理

摘要:推理视觉语言模型(VLMs)在复杂的多模态任务上表现出了良好的性能。 然而,它们仍然面临着重大挑战:它们对推理错误高度敏感,需要大量带注释的数据或精确的验证器,并且难以在特定领域之外进行…

正则表达式在Java中的应用(补充)

正则表达式在Java中的应用 Java通过java.util.regex包提供正则表达式支持,核心类包括Pattern和Matcher。Pattern用于编译正则表达式模式,Matcher用于匹配操作。基本语法遵循标准正则规则,如\d匹配数字,\w匹配单词字符。 Pattern…

C++ 内存泄漏检测器设计

文章目录 1. C中的动态内存分配2. 什么是内存泄漏3. 内存泄漏的代码案例4. 内存泄漏检查器的设计模块1:位置信息捕获:模块2:内存分配跟踪:模块3:内存释放跟踪:模块4:泄漏记录存储:模…

线程安全与线程池

概念:多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题。 出现线程安全问题的条件,原因:1.存在多个线程在同时执行 2.同时访问一个共享资源 3.存在修改该共享资源 线程同步:是线程安全…

网络安全的学习路线是怎么样的?

我是几乎完全自学的,十年前从双非跨专业考研到中科大软件学院网络安全专业,读研之前,C语言是自学的,数据结构是自学的,计算机网络是自学的,操作系统是自学的,微机原理是自学的。为了让我们能跟上…

每日算法-250602

每日算法学习记录 - 250602 今天学习和复习了两道利用前缀和与哈希表解决的子数组问题,特此记录。 560. 和为 K 的子数组 题目 思路 本题的核心思想是利用 前缀和 与 哈希表 来优化查找过程。 解题过程 题目要求统计和为 k 的子数组个数。 我们首先预处理出一…

Hadoop学习笔记

(1)Hadoop概述 Hadoop是一个开源的分布式计算和存储框架,用于处理大规模数据集(大数据)的并行处理。它由Apache基金会开发,核心设计灵感来自Google的MapReduce和Google文件系统(GFS&#xff09…