编译期初始化
静态常量
const MAX_ID: usize = usize::MAX / 2; fn main() { println!("用户ID允许的最大值是{}",MAX_ID); }
- 关键字是const而不是let
- 定义常量必须指明类型(如 i32)不能省略
- 定义常量时变量的命名规则一般是全部大写
- 常量可以在任意作用域进行定义,其生命周期贯穿整个程序的生命周期。编译时编译器会尽可能将其内联到代码中,所以在不同地方对同一常量的引用并不能保证引用到相同的内存地址
- 常量的赋值只能是常量表达式/数学表达式,也就是说必须是在编译期就能计算出的值,如果需要在运行时才能得出结果的值比如函数,则不能赋值给常量表达式
- 对于变量出现重复的定义(绑定)会发生变量遮盖,后面定义的变量会遮住前面定义的变量,常量则不允许出现重复的定义
静态变量
static mut REQUEST_RECV: usize = 0; fn main() { unsafe { REQUEST_RECV += 1; assert_eq!(REQUEST_RECV, 1); } }
Rust 要求必须使用unsafe语句块才能访问和修改static变量,因为这种使用方式往往并不安全,其实编译器是对的,当在多线程中同时去修改时,会不可避免的遇到脏数据。
只有在同一线程内或者不在乎数据的准确性时,才应该使用全局静态变量。
原子类型
想要全局计数器、状态控制等功能,又想要线程安全的实现,原子类型是非常好的办法。
use std::sync::atomic::{AtomicUsize, Ordering}; static REQUEST_RECV: AtomicUsize = AtomicUsize::new(0); fn main() { for _ in 0..100 { REQUEST_RECV.fetch_add(1, Ordering::Relaxed); } println!("当前用户请求数{:?}",REQUEST_RECV); }
运行期初始化
静态初始化有一个致命的问题:无法用函数进行静态初始化,例如你如果想声明一个全局的Mutex锁:
use std::sync::Mutex; static NAMES: Mutex<String> = Mutex::new(String::from("Sunface, Jack, Allen")); fn main() { let v = NAMES.lock().unwrap(); println!("{}",v); }
运行后报错如下:
error[E0015]: calls in statics are limited to constant functions, tuple structs and tuple variants --> src/main.rs:3:42 | 3 | static NAMES: Mutex<String> = Mutex::new(String::from("sunface"));
lazy_static
lazy_static是社区提供的非常强大的宏,用于懒初始化静态变量,之前的静态变量都是在编译期初始化的,因此无法使用函数调用进行赋值,而lazy_static允许我们在运行期初始化静态变量!
use std::sync::Mutex; use lazy_static::lazy_static; lazy_static! { static ref NAMES: Mutex<String> = Mutex::new(String::from("Sunface, Jack, Allen")); } fn main() { let mut v = NAMES.lock().unwrap(); v.push_str(", Myth"); println!("{}",v); }
Box::leak
Rust为我们提供了Box::leak方法,它可以将一个变量从内存中泄漏,然后将其变为’static生命周期,最终该变量将和程序活得一样久,因此可以赋值给全局静态变量CONFIG。
#[derive(Debug)] struct Config { a: String, b: String } static mut CONFIG: Option<&mut Config> = None; fn main() { let c = Box::new(Config { a: "A".to_string(), b: "B".to_string(), }); unsafe { // 将`c`从内存中泄漏,变成`'static`生命周期 CONFIG = Some(Box::leak(c)); println!("{:?}", CONFIG); } }
从函数中返回全局变量
#[derive(Debug)] struct Config { a: String, b: String, } static mut CONFIG: Option<&mut Config> = None; fn init() -> Option<&'static mut Config> { Some(&mut Config { a: "A".to_string(), b: "B".to_string(), }) } fn main() { unsafe { CONFIG = init(); println!("{:?}", CONFIG) } }
标准库中的 OnceCell
在 Rust 标准库中提供了实验性的 lazy::OnceCell 和 lazy::SyncOnceCell (在 Rust 1.70.0版本及以上的标准库中,替换为稳定的 cell::OnceCell 和 sync::OnceLock )两种 Cell ,前者用于单线程,后者用于多线程,它们用来存储堆上的信息,并且具有最 多只能赋值一次的特性。 如实现一个多线程的日志组件 Logger:
// 低于Rust 1.70版本中, OnceCell 和 SyncOnceCell 的API为实验性的 , // 需启用特性 `#![feature(once_cell)]`。 // #![feature(once_cell)] // use std::{lazy::SyncOnceCell, thread}; // Rust 1.70版本以上, use std::{sync::OnceLock, thread}; fn main() { // 子线程中调用 let handle = thread::spawn(|| { let logger = Logger::global(); logger.log("thread message".to_string()); }); // 主线程调用 let logger = Logger::global(); logger.log("some message".to_string()); let logger2 = Logger::global(); logger2.log("other message".to_string()); handle.join().unwrap(); } #[derive(Debug)] struct Logger; // 低于Rust 1.70版本 // static LOGGER: SyncOnceCell<Logger> = SyncOnceCell::new(); // Rust 1.70版本以上 static LOGGER: OnceLock<Logger> = OnceLock::new(); impl Logger { fn global() -> &'static Logger { // 获取或初始化 Logger LOGGER.get_or_init(|| { println!("Logger is being created..."); // 初始化打印 Logger }) } fn log(&self, message: String) { println!("{}", message) } }
以上代码我们声明了一个 global() 关联函数,并在其内部调用 get_or_init 进行初始化 Logger,之后在不同线程上多次调用 Logger::global() 获取其实例:
Logger is being created…
some message
other message
thread message
可以看到,Logger is being created… 在多个线程中使用也只被打印了一次。