Rust中的全局静态变量(涉及到 match、lazy_static! 、phf)

lihn 2019-09-06

一、全局可变变量

一般避免使用全局变量。 取而代之,尽早在某处构建对象(比如在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/...

相关推荐