首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >28-Rust 教程 - 常量与静态

28-Rust 教程 - 常量与静态

作者头像
LarryLan
发布2026-05-29 13:08:49
发布2026-05-29 13:08:49
490
举报

常量与静态

全局数据的艺术:const、static 和 lazy_static

🎬 引入

你有没有想过这个问题:

代码语言:javascript
复制
// 这个应该用 const 还是 static?
const MAX_SIZE: i32 = ;
static COUNTER: i32 = ;

// 还有这个 lazy_static 是啥?
lazy_static! {
    static ref CONFIG: Config = Config::new();
}

Rust 里有三种"全局变量":conststaticlazy_static。它们看起来差不多,但用法和语义差别很大。

今天咱们就聊聊这三兄弟的区别和用法。

📌 核心概念

三兄弟对比

特性

const

static

lazy_static

求值时机

编译时

程序启动时

第一次使用时

内存地址

无(内联)

有固定地址

有固定地址

可变性

不可变

可 mutable

不可变(内部可变)

生命周期

'static

'static

'static

使用场景

常量值

全局状态

延迟初始化

生活化类比:

  • const:像数学公式——π = 3.14159...,用的时候直接代入
  • static:像公告板——固定位置,大家都去看
  • lazy_static:像自动售货机——第一次有人买才进货

const 的特点

代码语言:javascript
复制
// 语法
const NAME: Type = value;

// 特点
- 编译时求值
- 内联展开(无地址)
- 必须是常量表达式
- 类型注解必需

static 的特点

代码语言:javascript
复制
// 语法
static NAME: Type = value;

// 特点
- 程序启动时求值
- 有固定内存地址
- 生命周期是 'static
- 可变 static 需要 unsafe

lazy_static 的特点

代码语言:javascript
复制
// 语法(需要宏)
lazy_static! {
    static ref NAME: Type = value;
}

// 特点
- 第一次使用时求值
- 支持复杂初始化
- 线程安全
- 需要额外依赖

💻 代码示例

基础示例:const

代码语言:javascript
复制
// 基本用法
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 的使用场景

代码语言:javascript
复制
// 数组大小
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 的基础用法

代码语言:javascript
复制
// 不可变 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);
}

mutable static(需要 unsafe)

代码语言:javascript
复制
static mut COUNTER: i32 = ;

fn increment() {
    unsafe {
        COUNTER += ;
        println!("Counter: {}", COUNTER);
    }
}

fn main() {
    increment();  // Counter: 1
    increment();  // Counter: 2
}

⚠️ 警告:

可变 static 在多线程下不安全!别这么用,除非你知道自己在做什么。

安全替代方案:

代码语言:javascript
复制
use std::sync::atomic::{AtomicUsize, Ordering};

static COUNTER: AtomicUsize = AtomicUsize::new();

fn increment() {
    COUNTER.fetch_add(, Ordering::SeqCst);
    println!("Counter: {}", COUNTER.load(Ordering::SeqCst));
}

lazy_static 基础

代码语言:javascript
复制
// 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"));
}

lazy_static 的复杂初始化

代码语言:javascript
复制
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 确保只编译一次。

once_cell(现代替代方案)

代码语言:javascript
复制
// 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_celllazy_static 的现代替代品,语法更简洁,性能更好。Rust 1.70+ 甚至已经内置了 std::sync::OnceLock

标准库的 OnceLock(Rust 1.70+)

代码语言:javascript
复制
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());
}

🐛 常见坑点

坑点 1:const 用了非常量表达式

代码语言:javascript
复制
// ❌ 编译错误
const SIZE: usize = vec![, , ].len();  // vec! 不是常量表达式

// ✅ 正确
const SIZE: usize = ;

编译器在说什么人话?

"const 初始化必须是常量表达式,vec! 是运行时宏!"

坑点 2:static 的生命周期误解

代码语言:javascript
复制
static S: &str = "hello";  // ✅ 字面量有 'static 生命周期

fn main() {
    let s = String::from("world");
    static S2: &str = &s;  // ❌ 编译错误!
}

编译器在说什么?

"s 在函数结束时会被销毁,但 static 要活到程序结束,不行!"

坑点 3:可变 static 的线程安全问题

代码语言:javascript
复制
static mut COUNTER: i32 = ;

fn main() {
    // 多线程访问
    std::thread::spawn(|| {
        unsafe { COUNTER += ; }  // ❌ 数据竞争!
    });
    
    std::thread::spawn(|| {
        unsafe { COUNTER += ; }  // ❌ 数据竞争!
    });
}

正确做法:

代码语言:javascript
复制
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);
    });
}

坑点 4:lazy_static 的循环依赖

代码语言:javascript
复制
lazy_static! {
    static ref A: String = B.to_uppercase();  // ❌ B 还没初始化
    static ref B: String = "hello".to_string();
}

解决方案:

避免循环依赖,重新设计结构。

坑点 5:const 和 static 混用

代码语言:javascript
复制
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);  // 始终相同
}

🎯 实战案例

案例 1:应用配置

代码语言:javascript
复制
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
}

案例 2:全局日志器

代码语言:javascript
复制
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!("应用启动");
}

案例 3:常量映射表

代码语言:javascript
复制
// 编译时生成的查找表
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"
}

案例 4:线程安全的全局计数器

代码语言:javascript
复制
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)
}

🧠 思维导图

28-常量与静态
28-常量与静态

📝 小结

金句回顾:

  1. const 是编译时常量——内联展开,无地址
  2. static 有固定地址——可变需要 unsafe,用原子类型更安全
  3. lazy_static 延迟初始化——第一次使用时才计算
  4. once_cell 是现代替代——语法更简洁,性能更好
  5. 避免可变全局状态——用原子类型或同步原语

下篇预告:

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

🔗 参考资料

  • Constants and Statics
  • lazy_static
  • once_cell
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-05-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Larry的Hub 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 常量与静态
    • 🎬 引入
    • 📌 核心概念
      • 三兄弟对比
      • const 的特点
      • static 的特点
      • lazy_static 的特点
    • 💻 代码示例
      • 基础示例:const
      • const 的使用场景
      • static 的基础用法
      • mutable static(需要 unsafe)
      • lazy_static 基础
      • lazy_static 的复杂初始化
      • once_cell(现代替代方案)
      • 标准库的 OnceLock(Rust 1.70+)
    • 🐛 常见坑点
      • 坑点 1:const 用了非常量表达式
      • 坑点 2:static 的生命周期误解
      • 坑点 3:可变 static 的线程安全问题
      • 坑点 4:lazy_static 的循环依赖
      • 坑点 5:const 和 static 混用
    • 🎯 实战案例
      • 案例 1:应用配置
      • 案例 2:全局日志器
      • 案例 3:常量映射表
      • 案例 4:线程安全的全局计数器
    • 🧠 思维导图
    • 📝 小结
    • 🔗 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档