Rust中的全局静态变量(涉及到 match、lazy_static! 、phf)
一、全局可变变量
一般避免使用全局变量。 取而代之,尽早在某处构建对象(比如在main中),然后将对该对象的可变引用传递到需要它的位置。 这通常会使您的代码更易读。
在决定你想要全局可变变量之前,先仔细思考。 在极少数情况下它很有用,所以还是值得知道怎么使用。
1.1 如何使用 lazy_static! 创建全局可变变量的例子
lazy-static crate 可以取代一些创建单例的复杂代码。 以下是一个全局可变 vector:
#[macro_use] extern crate lazy_static; use std::sync::Mutex; lazy_static! { static ref ARRAY: Mutex<Vec<u8>> = Mutex::new(vec![]); } fn do_a_call() { ARRAY.lock().unwrap().push(1); } fn main() { do_a_call(); do_a_call(); do_a_call(); println!("called {}", ARRAY.lock().unwrap().len()); }
然而,全局静态变量是我们经常使用的,必须找到高效的方式来创建和查找全局静态变量。下面我们讲述如何在rust中使用全局静态变量。
二、创建全局静态变量的三种方式
静态变量在程序的整个生命周期中都可用。 它们被分配在编译时已知的内存块中。 因此,它们往往代表程序可以访问的全局状态。 如果一个静态变量依赖于另一个静态变量,那就变得特别棘手。 一些语言社区甚至谈论静态初始化顺序问题的惨败(看着你,C ++)。 其他像C一样,只允许使用常量/表达式进行静态初始化,Rust也属于这个群体。 但有其它选择......
假设我们正在构建一个Web浏览器引擎。 在成千上万要关注的事情中,我们应该能够呈现彩色文本。 <p style =“color:blue”>应该看起来像是以蓝色字体设置的段落。 但蓝色是人类可读的颜色名称,计算机只能读懂数字。 定义Color结构体:
#[derive(Clone, Debug)] pub struct Color { r: u8, g: u8, b: u8, }
2.1 match
我们无法创建静态HashMap并初始化数据:key为颜色名称、value为颜色。 首先想到,我们可以使用模式匹配来按名称查找颜色:
pub fn find_color(name: &str) -> Option<Color> { match name.to_lowercase().as_str() { "amber" => Some(Color { r: 255, g: 191, b: 0 }), // hundreds of other names... "zinnwaldite brown" => Some(Color { r: 44, g: 22, b: 8 }), _ => None, } }
缺点是匹配字符串切片是一个线性递增的搜索:拥有的颜色越多,find_color就越慢。我们可以创建一个静态HashMap吗?
2.2lazy_static!宏
2.2.1 lazy_static!的作用
引用:https://zhuanlan.zhihu.com/p/...
我们会遇到如下场景:
1、当我们想初始化一些静态变量,这当然没问题。例如: static AGE:u32 = 18; static NAME:&str = "hery"; static ARRAY:[u8;2] = [0x18u8, 0x11u8]; 2、有没有想过初始化动态的数组,vector,map?结果是编译不通, 例如: static VEC:Vec<u8> = vec![0x18u8, 0x11u8]; static MAP: HashMap<u32, String> = HashMap::new(); error: `<std::vec::Vec<T>>::new` is not yet stable as a const fn --> src\bin\u-lazy-static.rs:21:22 | 21 | static VEC:Vec<u8> = Vec::new(); | ^^^^^^^^^^ | = help: in Nightly builds, add `#![feature(const_vec_new)]` to the crate attributes to enable error[E0015]: calls in statics are limited to constant functions, tuple structs and tuple variants --> src\bin\u-lazy-static.rs:22:36 | 22 | static MAP: HashMap<u32, String> = HashMap::new(); | E0015 错误提示: 只有 const 类型函数能被静态或常量表达式调用。 3、我还想在使用函数初始化静态变量,这也编译不通过: fn mulit(i: u32) -> u32 { i * 2 } static PAGE:u32 = mulit(18);
接下去使用 lazy_static! 消除上面的所有问题。
#[macro_use] extern crate lazy_static; use std::collections::HashMap; lazy_static! { static ref VEC:Vec<u8> = vec![0x18u8, 0x11u8]; static ref MAP: HashMap<u32, String> = { let mut map = HashMap::new(); map.insert(18, "hury".to_owned()); map }; static ref PAGE:u32 = mulit(18); } fn mulit(i: u32) -> u32 { i * 2 } fn main() { println!("{:?}", *PAGE); println!("{:?}", *VEC); println!("{:?}", *MAP); }
2.2.2回到颜色的例子
lazy_static! 是一个允许以非平凡的方式初始化的静态变量的包。 例如,预先计算的常规表达式,例如docopt中使用的表达式,或静态HashMap。
#[macro_use] extern crate lazy_static; use std::collections::HashMap; lazy_static! { static ref COLORS_MAP: HashMap<&'static str, Color> = { let mut map = HashMap::new(); map.insert("amber", Color { r: 255, g: 191, b: 0 }); // ... map.insert("zinnwaldite brown", Color { r: 44, g: 22, b: 8 }); map }; } pub fn find_color_lazy_static(name: &str) -> Option<Color> { COLORS_MAP.get(name.to_lowercase().as_str()).cloned() }
COLORS_MAP将在首次访问时进行初始化。 我们现在可以安全地将其视为常规静态变量。
2.3phf
HashMap使用有点慢的哈希算法(引用文档)来避免DoS攻击。 在数据量足够大的map中有可能发生冲突。
另一方面,phf使用完美散列(散列,保证不冲突)来构建编译时map。 这样我们就可以在运行时进行有效的恒定时间查找。
#![feature(plugin)] #![plugin(phf_macros)] #[macro_use] extern crate phf; static COLORS: phf::Map<&'static str, Color> = phf_map! { "amber" => Color { r: 255, g: 191, b: 0 }, // ... "zinnwaldite brown" => Color { r: 44, g: 22, b: 8 }, }; pub fn find_color_phf(name: &str) -> Option<Color> { COLORS.get(name.to_lowercase().as_str()).cloned() }
2.4Benchmarks
以上三种方式的效率对比:
#[cfg(test)] mod tests { use super::*; use test::Bencher; #[bench] fn bench_match_lookup(b: &mut Bencher) { b.iter(|| find_color("White")) } #[bench] fn bench_lazy_static_map(b: &mut Bencher) { b.iter(|| find_color_lazy_static("White")) } #[bench] fn bench_phf_map(b: &mut Bencher) { b.iter(|| find_color_phf("White")) } } $ cargo bench running 3 tests test tests::bench_lazy_static_map ... bench: 367 ns/iter (+/- 20) test tests::bench_match_lookup ... bench: 3,948 ns/iter (+/- 460) test tests::bench_phf_map ... bench: 350 ns/iter
正如预期的那样,match语句中运行时的线性搜索是最慢的。 静态HashMap和phf::Map都快了一个数量级,后者领先一点点。 我个人更喜欢使用phf,因为它的意图就是创建静态编译时map。 lazy_static的意图更为通用,初始化map只是其众多用途之一。
三、参考文章:
《how-do-i-create-a-global-mutable-singleton》https://stackoverflow.com/que...
《perfect hash fucnction》https://en.wikipedia.org/wiki...
《match-statement-efficiency》https://users.rust-lang.org/t...
《Static initializers will murder your family》https://meowni.ca/posts/stati...
《Rust Crate 使用:lazy_static》https://zhuanlan.zhihu.com/p/...