Rust-01变量绑定与解构
👉 手动设置变量
🍎 手动设置变量的原因
大多数语言中,要么只支持声明可变的变量(为编程提供了灵活性)
要么只支持声明不可变的变量(例如函数式语言--为编程提供了安全性)
而 Rust 选择了两者都要,既要灵活性又要安全性,还有运行性能上的提升
🍎常见语言的可变变量和不可变变量
// JavaScript
// 可变变量
let x = 10;
x = 20; // 有效,let 声明的变量是可变的
// 不可变常量
const y = 30;
// y = 40; // 报错,const 声明的变量是不可变的
//Python
# 可变变量
x = 10
x = 20 # 这是有效的,变量 x 是可变的
# 不可变对象
x = (1, 2, 3) # 创建一个元组
# x[0] = 10 # 会抛出错误:'tuple' object does not support item assignment
// Java
// 声明可变和不可变的变量,使用final 将变量声明为不可变的(常量)
// 可变变量
int x = 10;
x = 20; // 这是有效的,变量 x 是可变的
// 不可变变量
final int y = 30;
// y = 40; // 编译错误,不能修改 final 变量 y
👉变量命名
同理,一般遵循Rust的命名规范,排除保留字和额外留存的关键字(keywords)
🍎 现在在使用的关键字
关键字 | 描述 |
---|---|
as | 强制类型转换,或use 和 extern crate包和模块引入语句中的重命名 |
break | 立刻退出循环 |
const | 定义常量或原生常量指针(constant raw pointer) |
continue | 继续进入下一次循环迭代 |
crate | 链接外部包 |
dyn | 动态分发特征对象 |
else | 作为 if 和 if let 控制流结构的 fallback |
enum | 定义一个枚举类型 |
extern | 链接一个外部包, 或者一个宏变量(该变量定义在另外一个包中) |
false | 布尔值 false |
fn | 定义一个函数或 函数指针类型 (function pointer type) |
for | 遍历一个迭代器或实现一个 trait 或者指定一个更高级的生命周期 |
if | 基于条件表达式的结果来执行相应的分支 |
impl | 为结构体或者特征实现具体功能 |
in | for 循环语法的一部分 |
let | 绑定一个变量 |
loop | 无条件循环 |
match | 模式匹配 |
mod | 定义一个模块 |
move | 使闭包获取其所捕获项的所有权 |
mut | 在引用、裸指针或模式绑定中使用,表明变量是可变的 |
pub | 表示结构体字段、impl 块或模块的公共可见性 |
ref | 通过引用绑定 |
return | 从函数中返回 |
Self | 实现特征类型的类型别名 |
self | 表示方法本身或当前模块 |
static | 表示全局变量或在整个程序执行期间保持其生命周期 |
struct | 定义一个结构体 |
super | 表示当前模块的父模块 |
trait | 定义一个特征 |
true | 布尔值 true |
type | 定义一个类型别名或关联类型 |
unsafe | 表示不安全的代码、函数、特征或实现 |
use | 在当前代码范围内(模块或者花括号对)引入外部的包、模块等 |
where | 表示一个约束类型的从句 |
while | 基于一个表达式的结果判断是否继续循环 |
🍎保留以备将来使用
跟其他语言差不多会预留一些关键变量,同样一些原生的我们也尽量不进行使用
关键字 | 描述 |
---|---|
abstract | 保留以备将来使用 |
async | 保留以备将来使用 |
await | 保留以备将来使用 |
become | 保留以备将来使用 |
box | 保留以备将来使用 |
do | 保留以备将来使用 |
final | 保留以备将来使用 |
macro | 保留以备将来使用 |
override | 保留以备将来使用 |
priv | 保留以备将来使用 |
try | 保留以备将来使用 |
typeof | 保留以备将来使用 |
unsized | 保留以备将来使用 |
virtual | 保留以备将来使用 |
yield | 保留以备将来使用 |
🍎原生类型的
这里面就是一些c和其他之类的关键字和保留字,参考我们学的c语法即可
🍎原生标识符
当然,如果上面的说法你都反对,也可以使用原生标识符(Raw identifiers)帮助我们使用通常不能使用的关键字,带有 r#
前缀
比如我现在使用match
作为函数名
fn match(needle: &str, haystack: &str) -> bool {
haystack.contains(needle)
}
这个时候报警告给我,也会友好的给我们一个写法提示
error: expected identifier, found keyword `match`
--> src\modules\user.rs:30:4
|
30 | fn match(needle: &str, haystack: &str) -> bool {
| ^^^^^ expected identifier, found keyword
|
help: escape `match` to use it as an identifier
|
30 | fn r#match(needle: &str, haystack: &str) -> bool {
| ++
加上我们前缀以后,ok,这个时候运行和输出都正常
fn r#match(needle: &str, haystack: &str) -> bool {
haystack.contains(needle)
}
fn main() {
assert!(r#match("foo", "foobar"));
println!("World, hello");
}
// World, hello
原生标识符允许我们使用任何单词作为标识符,即使该单词恰好是保留关键字。
还允许我们使用其它 Rust 版本编写的库,也就是旧版本你的代码迁移过来往前面加一个r#
照样能够使用
fn main() {
let r#async = 10;
let r#await = 20;
println!("r#async: {}, r#await: {}", r#async, r#await);
}
👉变量绑定
Rust把赋值的过程起了另一个名字:变量绑定,其实主要也是为了强调Rust 核心原则——所有权
let a = "hello world"
👉变量可变性
Rust 的变量默认不可变,但通过 mut
关键字我们可以让变量变为可变的
🍎好处
要改变不可变变量,就要重新创建一个新的变量,涉及到内存对象的再分配
而可变变量最大的好处就是使用上的灵活性和性能上的提升
🍎示例
下面这段代码运行会给我们一个报错提示
fn main() {
let x = 5;
println!("The value of x is: {}", x);
x = 6;
println!("The value of x is: {}", x);
}
报错cannot assign twice to immutable variable x
(无法对不可变的变量进行重复赋值)
--> src\main.rs:26:5
|
24 | let x = 5;
| - first assignment to `x`
25 | println!("The value of x is: {}", x);
26 | x = 6;
| ^^^^^ cannot assign twice to immutable variable
|
help: consider making this binding mutable
|
24 | let mut x = 5;
使用mut
以后运行正常并输出
fn main() {
let mut x = 5;
println!("The value of x is: {}", x);
x = 6;
println!("The value of x is: {}", x);
}
The value of x is: 5
The value of x is: 6
👉使用下划线
忽略未使用的变量
未使用的变量Rust 会给我们一个警告
let z=5;
--> src\main.rs:24:9
|
24 | let z= 5;
| ^ help: if this is intentional, prefix it with an underscore: `_z`
可以使用下划线
忽略这个未使用变量
的警告
fn main() {
let _z= 5;
}
👉变量解构
let
表达式不仅用于变量的绑定,还能进行复杂变量的解构,这里跟前端的JS的解构十分类似
fn main() {
let (a, mut b): (bool,bool) = (true, false);
// a = true,不可变; b = false,可变
println!("a = {:?}, b = {:?}", a, b);
b = true;
assert_eq!(a, b);
}
// 这里assert_eq! 这个宏的作用如下
assert_eq!宏在测试失败时,会自动输出一条错误消息
这条消息包含了 a 和 b 的值,以及发生 panic 的位置
🍎 Rust 1.59
版本后,在赋值语句的左式中使用元组、切片和结构体模式了
我们可以分别从几种结构之中进行解构赋值,从下面的赋值可以看出,无论采取哪种解构赋值均可
fn main() {
let (a, b, c, d, e); // 声明变量但不进行初始化
(a, b) = (1, 2); // 元组模式匹配
[c, .., d, _] = [1, 2, 3, 4, 5]; // 数组模式匹配 // _ 代表匹配一个我们不关心的具体值
Struct { e, .. } = Struct { e: 5 }; // 结构体模式匹配
print!("{a}, {b}, {c}, {d}, {e}"); // 输出 1, 2, 1, 4, 5
assert_eq!([1, 2, 1, 4, 5], [a, b, c, d, e]);
}
//输出 1, 2, 1, 4, 5
需要注意
- 使用方式跟
let
保持一致,但是let
会重新绑定,这里是对之前绑定的变量进行再赋值 - 使用
+=
的赋值语句还不支持解构式赋值
👉变量与常量差异
在rust之中我们声明变量常量如下
let
声明变量,const
声明常量且通常大写
(Rust 常量的命名约定是全部字母都使用大写,并使用下划线分隔单词,另外对数字字面量可插入下划线以提高可读性)
const MAX_USERS: u32 = 100; // 常量,不能改变
let mut current_users = 50; // 变量,初始值为 50
// 修改变量的值
current_users += 10; // 变为 60
// 你不能修改常量的值
// MAX_USERS = 200; // 编译错误
总结一下区别大致如下:
特性 | 变量(Variables) | 常量(Constants) |
---|---|---|
可变性 | 默认不可变,使用 mut 可变更改值 | 不可变 |
生命周期 | 局部作用域内,超出作用域会销毁 | 在整个程序中存在 |
类型推导 | 可以推导类型,也可以显式声明 | 必须显式声明类型 |
计算时机 | 可以依赖运行时计算 | 必须在编译时计算 |
作用域 | 局部作用域,有限制 | 全局作用域,可在任意地方访问 |
实际应用 | 通常用于在程序中存储临时值,例如计数器、用户输入等。因为其可以在运行时进行修改,适合存储需要动态更新的值。 | 通常用于存储不变的值,例如程序配置、物理常数、应用中的限制值等。常量是编译时就确定的,因此在运行时不会有性能开销。 |
👉变量遮蔽
Rust 允许声明相同的变量名,后面声明的变量会遮蔽掉前面声明的
fn main() {
let x = 5;
// 在main函数的作用域内对之前的x进行遮蔽
let x = x + 1;
{
// 在当前的花括号作用域内,对之前的x进行遮蔽
let x = x * 2;
println!("花括号内的x: {}", x);
}
println!("main函数内的x: {}", x);
}
//输出如下
花括号内的x: 12
main函数内的x: 6
关于这里Rust的变量遮蔽涉及到内存对象的再分配吗?
个人理解
变量遮蔽只是创建了一个新的变量,并使用旧变量的值进行初始化
而不是分配新的内存空间
在 Rust 中,内存管理是自动的,当变量离开作用域时
它们使用的内存会被自动释放。
Rust中文圣经里面给我们的提示是说涉及到了内存对象的再分配,所以这里暂时还是个疑问,有理解的朋友可以发评论帮我解疑,为此我也建立了一个Rust学习群,帮助大家一起解答,私信我添加。
🍎原文是这样子的
和 mut 变量的使用是不同的
第二个 let 生成了完全不同的新变量,两个变量只是恰好拥有同样的名称,涉及一次内存对象的再分配 ,而 mut 声明的变量,可以修改同一个内存地址上的值
并不会发生内存对象的再分配,性能要更好。
🍎更新2025-06-24
追加
应该是涉及到了内存对象的再分配,因为重新声明了