Rust-01变量绑定与解构

1 阅读10分钟

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为结构体或者特征实现具体功能
infor 循环语法的一部分
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追加

应该是涉及到了内存对象的再分配,因为重新声明了