变量与可变性:Rust 的内存哲学第一课
说实话,我刚开始学 Rust 时,最不习惯的就是它的变量系统。别的语言里,let 就是个声明关键字,但在 Rust 里,它背后藏着一整套内存安全的设计哲学。今天我们就来聊聊这个。
变量声明:let 的第一次亲密接触
在 Rust 里,声明一个变量用 let 关键字。就这么简单:
let x = 5;
println!("x 的值是:{}", x);
嗯,这里要注意。默认情况下,变量是不可变的。什么意思?就是你不能改它:
let x = 5;
x = 6; // 编译错误!
我第一次写 Rust 时就被这个坑过。当时心想:「什么鬼?变量不能变?」后来才明白,这是 Rust 故意设计的。不可变性让代码更容易推理,也避免了多线程下的数据竞争。
可变性:mut 的正确打开方式
如果你确实需要改变一个变量的值,那就加个 mut:
let mut y = 10;
y = 20; // 没问题
println!("y 现在是:{}", y);
说白了,mut 就是一把钥匙。你主动告诉编译器:「我知道这个变量会变,我负责保证它的安全。」
常量:const 的硬核规则
常量跟不可变变量有点像,但更严格。用 const 声明:
const MAX_POINTS: u32 = 100_000;
注意几个关键点:
- 必须显式标注类型(不能省略)
- 值必须在编译期确定
- 命名惯例是全大写加下划线
- 可以在任何作用域声明,包括全局
我个人习惯把配置类的魔法数字都写成 const。比如数据库连接池大小、超时时间这些。这样改起来方便,也不怕手滑写错。
变量遮蔽:Shadowing 的巧妙用法
这个特性很有意思。你可以用同一个名字声明新变量,把旧的「遮住」:
let x = 5;
let x = x + 1; // 遮蔽了之前的 x
let x = x * 2;
println!("x 最终是:{}", x); // 输出 12
你想想看,这跟 mut 有什么区别?
区别大了:
- 遮蔽可以改变变量的类型
- 遮蔽创建的是新变量,旧变量依然存在(只是被遮住了)
- 遮蔽可以改变不可变性
举个例子:
let spaces = " "; // 字符串类型
let spaces = spaces.len(); // 数字类型,没问题!
如果用 mut,上面这段代码会报错。因为 mut 不允许改变类型。我在处理用户输入时经常用这个技巧——先拿字符串,再转成数字,全程用一个名字,代码干净多了。
数据类型:标量类型
Rust 是静态类型语言,编译时必须知道所有变量的类型。但编译器很聪明,大部分时候能推断出来。
整数类型
| 长度 | 有符号 | 无符号 |
|---|---|---|
| 8 位 | i8 | u8 |
| 16 位 | i16 | u16 |
| 32 位 | i32 | u32 |
| 64 位 | i64 | u64 |
| 128 位 | i128 | u128 |
| 架构相关 | isize | usize |
默认整数类型是 i32。我个人建议:除非有特殊需求,否则就用 i32。性能好,兼容性强。
wrapping_add 等方法来显式处理溢出。
浮点类型
两种:f32 和 f64。默认是 f64:
let x = 2.0; // f64
let y: f32 = 3.0; // f32
浮点数比较要小心。我记得有次做金融计算,直接用 == 比较两个浮点数,结果出了大问题。后来改用 (a - b).abs() < epsilon 这种方式。
布尔类型
就两个值:true 和 false。大小是一个字节:
let is_ok = true;
let is_not: bool = false;
字符类型
Rust 的 char 是 Unicode 标量值,占 4 个字节。这意味着它能表示任何语言的字符:
let c = 'z';
let emoji = '😊';
let chinese = '中';
嗯,这里要注意。char 用单引号,字符串用双引号。别搞混了。
复合类型:元组和数组
元组:把不同类型打包
元组可以包含不同类型的值,长度固定:
let tup: (i32, f64, u8) = (500, 6.4, 1);
let (x, y, z) = tup; // 解构
println!("y 的值是:{}", y);
也可以用索引访问:
let first = tup.0;
let second = tup.1;
我在写函数返回多个值时特别喜欢用元组。比如返回 (状态码, 消息, 数据) 这种组合。
数组:同类型元素的集合
数组长度固定,所有元素类型相同:
let arr = [1, 2, 3, 4, 5];
let first = arr[0];
也可以显式声明类型和长度:
let arr: [i32; 5] = [1, 2, 3, 4, 5];
let zeros = [0; 3]; // [0, 0, 0]
知识体系总览
下面这张图帮你理清本章的知识结构:
这张图把本章的知识点串起来了。从中心的「变量与可变性」出发,向外辐射出六个核心概念。标量类型和复合类型又各自展开。你仔细看看,会发现 Rust 的变量系统其实很有条理。
- let 声明变量,默认不可变
- mut 开启可变性,需要你主动选择
- const 声明编译期常量,类型必须标注
- Shadowing 可以复用变量名,甚至改变类型
- 标量类型:整数、浮点、布尔、字符
- 复合类型:元组(不同类型打包)、数组(同类型集合)
这些概念是 Rust 的基石。我当年学的时候,花了一周才真正理解「不可变」的好处。现在回头看,这个设计太聪明了——它把很多运行时才能发现的问题,提前到了编译阶段。
好了,这一章就到这里。记住:变量声明不是小事,它关乎整个程序的安全性。多写、多练、多思考,你很快就能上手。
公众号:蓝海资料掘金营,微信deep3321