Rust中的全局靜態變量(涉及到 match、lazy_static! 、phf)

1、全局可變變量

通常避免使用全局變量。 取而代之,儘早在某處構建對象(好比在main中),而後將對該對象的可變引用傳遞到須要它的位置。 這一般會使您的代碼更易讀。html

在決定你想要全局可變變量以前,先仔細思考。 在極少數狀況下它頗有用,因此仍是值得知道怎麼使用。git

1.1 如何使用 lazy_static! 建立全局可變變量的例子

lazy-static crate 能夠取代一些建立單例的複雜代碼。 如下是一個全局可變 vector:github

#[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中使用全局靜態變量。算法

2、建立全局靜態變量的三種方式

靜態變量在程序的整個生命週期中均可用。 它們被分配在編譯時已知的內存塊中。 所以,它們每每表明程序能夠訪問的全局狀態。 若是一個靜態變量依賴於另外一個靜態變量,那就變得特別棘手。 一些語言社區甚至談論靜態初始化順序問題的慘敗(看着你,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/...post

咱們會遇到以下場景:

一、當咱們想初始化一些靜態變量,這固然沒問題。例如:
static AGE:u32 = 18;
static NAME:&str = "hery";
static ARRAY:[u8;2] = [0x18u8, 0x11u8];

二、有沒有想過初始化動態的數組,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 類型函數能被靜態或常量表達式調用。


三、我還想在使用函數初始化靜態變量,這也編譯不經過:
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只是其衆多用途之一。

3、參考文章:

《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/...

相關文章
相關標籤/搜索