《Rust 编程实战》系列第 8 篇
在上一篇文章中,我们学习了 Rust 的基础数据类型。
现在我们已经知道如何使用 let 定义变量,例如:
rust
let name = "Rust";
但是,并不是所有数据都会发生变化。
例如:
-
π(圆周率)
-
一天有 24 小时
-
HTTP 默认端口 80
-
HTTPS 默认端口 443
-
应用程序名称
-
API 版本号
这些数据通常在程序运行期间不会发生变化。
Rust 提供了两种方式来表示这类数据:
-
const(常量) -
static(静态变量)
很多初学者容易混淆它们。
本文将详细介绍它们的区别以及实际开发中的使用场景。
什么是常量?
常量(Constant)是程序运行过程中永远不会改变的数据。
使用:
rust
const
定义。
例如:
rust
const PI: f64 = 3.1415926;
fn main() {
println!("{}", PI);
}
输出:
text
3.1415926
const 的语法
语法如下:
rust
const 常量名: 类型 = 值;
例如:
rust
const MAX_SIZE: usize = 1024;
需要注意:
const 必须显式指定类型。
下面这样是不允许的:
rust
const MAX = 100;
正确写法:
rust
const MAX: i32 = 100;
这是 const 与 let 的一个重要区别。
常量命名规范
Rust 社区推荐:
全部使用大写字母。
多个单词:
使用下划线连接。
例如:
rust
const MAX_USER: usize = 100;
const API_VERSION: &str = "v1";
const APP_NAME: &str = "Rust CMS";
这样一眼就能知道:
这是常量。
const 可以定义在哪里?
可以在:
函数外:
rust
const PI: f64 = 3.14;
fn main() {
println!("{}", PI);
}
也可以:
函数内:
rust
fn main() {
const MAX: i32 = 100;
println!("{}", MAX);
}
都合法。
const 的特点
Rust 的常量具有以下特点:
-
必须指定类型
-
编译时确定值
-
不占用可变内存
-
不允许修改
-
可以在多个地方重复使用
例如:
rust
const YEAR: i32 = 2026;
以后:
任何地方:
都可以使用。
const 不能修改
例如:
rust
const MAX: i32 = 100;
fn main() {
MAX = 200;
}
编译:
直接报错。
因为:
常量就是:
不能修改。
const 可以参与计算
例如:
rust
const WIDTH: i32 = 100;
const HEIGHT: i32 = 50;
const AREA: i32 = WIDTH * HEIGHT;
因为:
这些计算:
编译时即可完成。
什么是静态变量?
Rust 还有另一种:
全局数据。
使用:
rust
static
例如:
rust
static APP_NAME: &str = "Rust Blog";
fn main() {
println!("{}", APP_NAME);
}
输出:
text
Rust Blog
看起来:
和:
rust
const
很像。
其实:
它们完全不同。
static 的特点
静态变量:
整个程序运行期间:
始终存在。
也就是说:
程序启动:
创建。
程序退出:
释放。
生命周期:
整个程序。
因此:
叫:
Static。
static 的语法
语法:
rust
static 名称: 类型 = 值;
例如:
rust
static VERSION: &str = "1.0.0";
也必须:
指定类型。
const 与 static 的区别
这是最容易混淆的地方。
| const | static |
|---|---|
| 编译时常量 | 全局变量 |
| 每次使用都会内联 | 只有一份数据 |
| 没有固定内存地址 | 有固定内存地址 |
| 生命周期不限于内存位置 | 生命周期贯穿整个程序 |
| 更推荐使用 | 使用场景较少 |
例如:
rust
const MAX: i32 = 100;
编译后:
很多地方:
直接:
替换:
text
100
不会真正创建一个全局变量。
而:
rust
static APP: &str = "Rust";
程序运行后:
内存中:
只有一份:
APP。
所有地方:
共享。
static mut
Rust 允许:
rust
static mut
例如:
rust
static mut COUNT: i32 = 0;
理论上:
可以:
修改。
例如:
rust
unsafe {
COUNT += 1;
}
但是:
需要:
rust
unsafe
为什么?
因为:
多个线程:
同时修改:
可能发生:
数据竞争。
因此:
Rust 不允许:
直接访问。
为什么尽量不要使用 static mut?
例如:
两个线程:
同时:
rust
COUNT += 1;
结果:
可能:
错误。
因此:
Rust 要求:
必须:
rust
unsafe
开发中:
几乎不会:
直接使用:
rust
static mut
如何共享全局可变数据?
Rust 推荐:
使用:
rust
Mutex
或者:
rust
RwLock
例如:
rust
static CONFIG: OnceLock<String> = OnceLock::new();
或者:
rust
static USERS: LazyLock<Vec<String>> = LazyLock::new(|| Vec::new());
在现代 Rust 中,更推荐使用标准库提供的 OnceLock、LazyLock,或者配合 Mutex、RwLock 实现线程安全的全局状态,而不是使用 static mut。
const 的常见使用场景
例如:
HTTP 状态码:
rust
const HTTP_OK: u16 = 200;
const HTTP_NOT_FOUND: u16 = 404;
或者:
配置:
rust
const APP_NAME: &str = "Rust CMS";
const VERSION: &str = "1.0";
再例如:
限制:
rust
const MAX_UPLOAD: usize = 1024 * 1024 * 10;
都非常适合。
static 的常见使用场景
例如:
程序名称:
rust
static APP_NAME: &str = "Rust Server";
日志:
rust
static LOGGER: Logger = ...;
全局配置:
rust
static CONFIG: OnceLock<Config> = OnceLock::new();
缓存:
rust
static CACHE: LazyLock<HashMap<...>> = ...;
这些对象:
整个程序:
只有:
一份。
let、const、static 对比
| 特性 | let | const | static |
|---|---|---|---|
| 是否可变 | 默认不可变,可加 mut |
否 | 默认否,static mut 可变但不推荐 |
| 是否需要类型 | 可推导 | 必须指定 | 必须指定 |
| 生命周期 | 当前作用域 | 编译期常量 | 整个程序 |
| 是否有固定内存地址 | 有 | 通常没有(内联) | 有 |
| 推荐用途 | 普通变量 | 常量 | 全局只读数据 |
常见错误
const 忘记写类型
错误:
rust
const MAX = 100;
正确:
rust
const MAX: i32 = 100;
修改 const
错误:
rust
const PI: f64 = 3.14;
PI = 3.15;
常量:
不能修改。
滥用 static mut
例如:
rust
static mut COUNT: i32 = 0;
虽然可以:
但是:
开发中:
几乎不用。
推荐:
使用:
rust
Mutex
RwLock
OnceLock
LazyLock
这些更安全。
最佳实践
开发过程中建议遵循以下原则:
-
普通变量使用
let。 -
固定不变的值使用
const。 -
全局共享且只读的数据使用
static。 -
全局可变状态优先使用
OnceLock、LazyLock配合Mutex或RwLock,不要直接使用static mut。 -
常量统一使用大写字母和下划线命名,保持代码风格一致。
本章小结
Rust 提供了三种不同的数据定义方式:
-
let:定义普通变量。 -
const:定义编译期常量。 -
static:定义程序运行期间一直存在的静态变量。
它们虽然看起来相似,但使用场景完全不同。
通过本文,你已经掌握了:
-
const的定义和特点 -
static的定义和特点 -
const与static的区别 -
为什么不推荐使用
static mut -
企业项目中推荐的全局状态管理方式
-
常见错误和最佳实践
理解这些内容后,你将在后续学习生命周期、模块系统以及多线程编程时更加得心应手。
下一篇预告
下一篇我们将学习 Rust 函数(Functions)。
包括:
-
如何定义函数
-
函数参数
-
返回值
-
表达式与语句
-
提前返回
-
单元类型
() -
函数的最佳实践
函数是 Rust 程序组织代码的基础,也是后续学习模块、Trait 和泛型的重要前提。