
全局数据的艺术:const、static 和 lazy_static
你有没有想过这个问题:
// 这个应该用 const 还是 static?
const MAX_SIZE: i32 = ;
static COUNTER: i32 = ;
// 还有这个 lazy_static 是啥?
lazy_static! {
static ref CONFIG: Config = Config::new();
}
Rust 里有三种"全局变量":const、static、lazy_static。它们看起来差不多,但用法和语义差别很大。
今天咱们就聊聊这三兄弟的区别和用法。
特性 | const | static | lazy_static |
|---|---|---|---|
求值时机 | 编译时 | 程序启动时 | 第一次使用时 |
内存地址 | 无(内联) | 有固定地址 | 有固定地址 |
可变性 | 不可变 | 可 mutable | 不可变(内部可变) |
生命周期 | 'static | 'static | 'static |
使用场景 | 常量值 | 全局状态 | 延迟初始化 |
生活化类比:
π = 3.14159...,用的时候直接代入// 语法
const NAME: Type = value;
// 特点
- 编译时求值
- 内联展开(无地址)
- 必须是常量表达式
- 类型注解必需
// 语法
static NAME: Type = value;
// 特点
- 程序启动时求值
- 有固定内存地址
- 生命周期是 'static
- 可变 static 需要 unsafe
// 语法(需要宏)
lazy_static! {
static ref NAME: Type = value;
}
// 特点
- 第一次使用时求值
- 支持复杂初始化
- 线程安全
- 需要额外依赖
// 基本用法
const MAX_SIZE: i32 = ;
const PI: f64 = 3.14159265359;
const GREETING: &str = "Hello, World!";
// 常量表达式
const DOUBLE_MAX: i32 = MAX_SIZE * ;
// 常量函数
const fn double(x: i32) -> i32 {
x *
}
const RESULT: i32 = double(); // 10
fn main() {
// const 会被内联展开
println!("{}", MAX_SIZE);
println!("{}", PI);
}
说人话:
const 在编译时就被替换成实际的值了,没有运行时开销。
// 数组大小
const BUFFER_SIZE: usize = ;
let buffer = [0u8; BUFFER_SIZE];
// 匹配模式
const MAX_RETRIES: u32 = ;
match retries {
..=MAX_RETRIES => println!("可以重试"),
_ => println!("超过最大重试次数"),
}
// 泛型常量参数
struct Array<T, const N: usize> {
data: [T; N],
}
let arr = Array::<i32, > {
data: [, , , , ],
};
// 不可变 static
static LANGUAGE: &str = "Rust";
const MAX_THREADS: usize = ;
fn main() {
// 可以访问
println!("{}", LANGUAGE);
// static 有地址
let addr = LANGUAGE as *const str as *const ();
println!("地址:{:p}", addr);
}
static mut COUNTER: i32 = ;
fn increment() {
unsafe {
COUNTER += ;
println!("Counter: {}", COUNTER);
}
}
fn main() {
increment(); // Counter: 1
increment(); // Counter: 2
}
⚠️ 警告:
可变 static 在多线程下不安全!别这么用,除非你知道自己在做什么。
安全替代方案:
use std::sync::atomic::{AtomicUsize, Ordering};
static COUNTER: AtomicUsize = AtomicUsize::new();
fn increment() {
COUNTER.fetch_add(, Ordering::SeqCst);
println!("Counter: {}", COUNTER.load(Ordering::SeqCst));
}
// Cargo.toml
[dependencies]
lazy_static = "1.4"
// 代码
use lazy_static::lazy_static;
use std::collections::HashMap;
lazy_static! {
static ref CONFIG: HashMap<String, String> = {
let mut m = HashMap::new();
m.insert("host".to_string(), "localhost".to_string());
m.insert("port".to_string(), "8080".to_string());
m
};
}
fn main() {
// 第一次访问时初始化
println!("{:?}", CONFIG.get("host"));
// 后续访问直接用
println!("{:?}", CONFIG.get("port"));
}
use lazy_static::lazy_static;
use regex::Regex;
lazy_static! {
// 编译正则表达式(耗时操作)
static ref EMAIL_REGEX: Regex = Regex::new(
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
).unwrap();
// 复杂配置
static ref DATABASE_URL: String = {
std::env::var("DATABASE_URL")
.unwrap_or_else(|_| "sqlite://app.db".to_string())
};
}
fn validate_email(email: &str) -> bool {
EMAIL_REGEX.is_match(email)
}
说人话:
正则表达式编译很耗时,用 lazy_static 确保只编译一次。
// Cargo.toml
[dependencies]
once_cell = "1.18"
// 代码
use once_cell::sync::Lazy;
use std::collections::HashMap;
// 更简洁的语法
static CONFIG: Lazy<HashMap<String, String>> = Lazy::new(|| {
let mut m = HashMap::new();
m.insert("host".to_string(), "localhost".to_string());
m
});
fn main() {
println!("{:?}", CONFIG.get("host"));
}
说人话:
once_cell 是 lazy_static 的现代替代品,语法更简洁,性能更好。Rust 1.70+ 甚至已经内置了 std::sync::OnceLock。
use std::sync::OnceLock;
static CONFIG: OnceLock<String> = OnceLock::new();
fn get_config() -> &'static String {
CONFIG.get_or_init(|| {
std::env::var("CONFIG").unwrap_or_else(|_| "default".to_string())
})
}
fn main() {
println!("{}", get_config());
}
// ❌ 编译错误
const SIZE: usize = vec![, , ].len(); // vec! 不是常量表达式
// ✅ 正确
const SIZE: usize = ;
编译器在说什么人话?
"const 初始化必须是常量表达式,vec! 是运行时宏!"
static S: &str = "hello"; // ✅ 字面量有 'static 生命周期
fn main() {
let s = String::from("world");
static S2: &str = &s; // ❌ 编译错误!
}
编译器在说什么?
"s 在函数结束时会被销毁,但 static 要活到程序结束,不行!"
static mut COUNTER: i32 = ;
fn main() {
// 多线程访问
std::thread::spawn(|| {
unsafe { COUNTER += ; } // ❌ 数据竞争!
});
std::thread::spawn(|| {
unsafe { COUNTER += ; } // ❌ 数据竞争!
});
}
正确做法:
use std::sync::atomic::{AtomicI32, Ordering};
static COUNTER: AtomicI32 = AtomicI32::new();
fn main() {
std::thread::spawn(|| {
COUNTER.fetch_add(, Ordering::SeqCst);
});
std::thread::spawn(|| {
COUNTER.fetch_add(, Ordering::SeqCst);
});
}
lazy_static! {
static ref A: String = B.to_uppercase(); // ❌ B 还没初始化
static ref B: String = "hello".to_string();
}
解决方案:
避免循环依赖,重新设计结构。
const X: i32 = ;
static Y: i32 = ;
fn main() {
let a = X; // ✅ const 内联
let b = Y; // ✅ static 拷贝
let ref_to_x = &X; // ⚠️ const 会临时创建
let ref_to_y = &Y; // ✅ static 有固定地址
println!("{:p}", ref_to_x); // 每次可能不同
println!("{:p}", ref_to_y); // 始终相同
}
use once_cell::sync::Lazy;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct Config {
database_url: String,
server_port: u16,
debug: bool,
}
static CONFIG: Lazy<Config> = Lazy::new(|| {
// 从环境变量或配置文件加载
dotenvy::dotenv().ok();
Config {
database_url: std::env::var("DATABASE_URL")
.unwrap_or_else(|_| "sqlite://app.db".to_string()),
server_port: std::env::var("SERVER_PORT")
.unwrap_or_else(|_| "8080".to_string())
.parse()
.unwrap_or(),
debug: std::env::var("DEBUG").is_ok(),
}
});
fn get_config() -> &'static Config {
&CONFIG
}
use once_cell::sync::Lazy;
use tracing_subscriber;
static LOGGER: Lazy<()> = Lazy::new(|| {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.init();
});
fn init_logger() {
let _ = &*LOGGER; // 确保初始化
}
fn main() {
init_logger();
tracing::info!("应用启动");
}
// 编译时生成的查找表
const HEX_DIGITS: [u8; ] = *b"0123456789abcdef";
fn to_hex(byte: u8) -> [u8; ] {
[
HEX_DIGITS[(byte >> ) as usize],
HEX_DIGITS[(byte & 0x0F) as usize],
]
}
fn main() {
let hex = to_hex();
println!("{}", std::str::from_utf8(&hex).unwrap()); // "ff"
}
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
static REQUEST_COUNT: AtomicUsize = AtomicUsize::new();
fn handle_request() {
let count = REQUEST_COUNT.fetch_add(, Ordering::SeqCst);
println!("第 {} 个请求", count + );
}
fn get_count() -> usize {
REQUEST_COUNT.load(Ordering::SeqCst)
}

金句回顾:
下篇预告:
咱们已经学了并发编程的基础(线程创建、join),但 Rust 的并发可不止这些。下篇聊聊消息传递,看看怎么用 channel 在线程之间安全地通信!