rust 中的 static

1. 名詞

2. 分類

與 static 相關的內容分爲四個分類,分別是 static lifetime/bound、static items、“static” 字段、“static methods”/associated function。

2.1. static lifetime/bound

在 rust 中並沒有把 static bound 單獨出來作爲一個概念,但是它與 static lifetime 確實不一樣,爲了方便理解,我們把 static bound 獨立出來。

2.1.1. 下面是代碼:

fn f(a: &'static i32) {
    println!("a's type:{},address: {:p}", std::any::type_name::<&'static i32>(), a);
}
fn f2<T: 'static>(a: &T) {
    println!("a's type:{},address: {:p}", std::any::type_name::<&T>(), a);
}
fn f3<T: 'static>(a: T) {
    println!("a's type:{},address: {:p}", std::any::type_name::<T>(), &a);
}
{
    println!("First part ====================================================");
    let data = 5;
    // f(&data);//`data` does not live long enough
    static data2: i32 = 8;
    print!("{:<30}","f(&data2);");
    f(&data2);
    print!("{:<30}","f(&&data2);");
    f(&&data2);//自動解引用,實際輸入參數爲 &data2
}
{
    println!("Second part ====================================================");
    let data = 5;
    print!("{:<30}","f2(&data);");
    f2(&data);//雖然data是局部變量,但是它仍然滿足'static要求。
    // f2(&&data);//編譯不通過“`data` does not live long enough”。data滿足'static要求,但是&data不滿足'static
    static data2: i32 = 8;
    println!("data address:{:p},data2 address:{:p}", &data, &data2);
    print!("{:<30}","f2(&data2);");
    f2(&data2);//f2::<i32>(&data2);
    print!("{:<30}","f2(&&data2);");
    f2(&&data2);//f2::<&i32>(&&data2);
    // f2(&&&data2);//f2::<&&i32>(&&data2); 編譯不通過“temporary value is freed at the end of this statement”
    println!("call f2::<i32>");
    print!("{:<30}","f2::<i32>(&data2);");
    f2::<i32>(&data2);
    print!("{:<30}","f2::<i32>(&&data2);");
    f2::<i32>(&&data2);//自動解引用,實際輸入參數爲 &data2
    print!("{:<30}","f2::<i32>(*&&&&data2);");
    f2::<i32>(*&&&data2);//自動解引用,實際輸入參數爲 &data2
    //上面三個函數調用,參數的值都是相等的,都是&data2,也就是data2的地址
    println!("call f2::<&i32>");
    print!("{:<30}","f2::<&i32>(&&data2);");
    f2::<&i32>(&&data2);//實際輸入參數爲 &&data2
    print!("{:<30}","f2::<&i32>(&&&data2);");
    f2::<&i32>(&&&data2);//自動解引用,實際輸入參數爲 &&data2
    print!("{:<30}","f2::<&i32>(**&&&&data2);");
    f2::<&i32>(**&&&&data2);//實際輸入參數爲 &&data2
    //上面三個函數調用,但是值不相等,但deref值(*&&data2 == &data2 )是相等的。第二次引用是一個臨時變化的地址
    // f2::<&&i32>(&&&data2);//編譯不通過“temporary value is freed at the end of this statement”
}
{
    println!("Third part ====================================================");
    println!("call f3");
    let data = 5;
    print!("{:<30}","f3(data);");
    f3(data);//f3::<i32> ---> f3(a: 'static i32)
    // f3(&data);//編譯不通過“`data` does not live long enough”
    static data2: i32 = 8;
    println!("data address:{:p},data2 address:{:p}", &data, &data2);
    print!("{:<30}","f3(&data2);");
    f3(&data2);
    print!("{:<30}","f3(data2);");
    f3(data2);
    // f3(&&data2);//error "temporary value dropped while borrowed"
    println!("call f3::<i32>");
    print!("{:<30}","f3::<i32>(*&data2);");
    f3::<i32>(*&data2);
    print!("{:<30}","f3::<i32>(**&&data2);");
    f3::<i32>(**&&data2);
    println!("call f3::<&i32>");
    print!("{:<30}","f3::<&i32>(&data2);");
    f3::<&i32>(&data2);
    print!("{:<30}","f3::<&i32>(&&data2);");
    f3::<&i32>(&&data2);
    //問題:當調用f3時,只要泛型參數相同(由於泛型的單態化,泛型參數相同,說明是同一個函數),輸出的地址也相同,如果f3(data)與f3(data2)輸出的地址相等。爲什麼?
    //原因之一是f3中輸出的是參數值的內存地址,而不是data或data2的地址。爲什麼同一個函數,參數地址相同,這個還沒有搞清楚是什麼原因
}

2.1.2. 運行的輸出結果如下:

First part ====================================================
f(&data2);                    a's type:&i32,address: 0x7ff785d9a908
f(&&data2);                   a's type:&i32,address: 0x7ff785d9a908
Second part ====================================================
f2(&data);                    a's type:&i32,address: 0x82fd8fe704
data address:0x82fd8fe704,data2 address:0x7ff785d9a90c
f2(&data2);                   a's type:&i32,address: 0x7ff785d9a90c
f2(&&data2);                  a's type:&&i32,address: 0x82fd8fe850
call f2::<i32>
f2::<i32>(&data2);            a's type:&i32,address: 0x7ff785d9a90c
f2::<i32>(&&data2);           a's type:&i32,address: 0x7ff785d9a90c
f2::<i32>(*&&&&data2);        a's type:&i32,address: 0x7ff785d9a90c
call f2::<&i32>
f2::<&i32>(&&data2);          a's type:&&i32,address: 0x82fd8fe9f0
f2::<&i32>(&&&data2);         a's type:&&i32,address: 0x82fd8fea48
f2::<&i32>(**&&&&data2);      a's type:&&i32,address: 0x82fd8feaa8
Third part ====================================================
call f3
f3(data);                     a's type:i32,address: 0x82fd8fe3bc
data address:0x82fd8feb14,data2 address:0x7ff785d9a910
f3(&data2);                   a's type:&i32,address: 0x82fd8fe3b8
f3(data2);                    a's type:i32,address: 0x82fd8fe3bc
call f3::<i32>
f3::<i32>(*&data2);           a's type:i32,address: 0x82fd8fe3bc
f3::<i32>(**&&data2);         a's type:i32,address: 0x82fd8fe3bc
call f3::<&i32>
f3::<&i32>(&data2);           a's type:&i32,address: 0x82fd8fe3b8
f3::<&i32>(&&data2);          a's type:&i32,address: 0x82fd8fe3b8

2.1.3. 從以結果可以得出如下:

static_lifetime: 

CommonRust Lifetime Misconceptions

(https://doc.rust-lang.org/rust-by-example/scope/lifetime/static_lifetime.html#trait-bound)

(https://github.com/pretzelhammer/rust-blog/blob/master/posts/common-rust-lifetime-misconceptions.md)

2.2.static items

2.2.1. 語法:

Static See: https://doc.rust-lang.org/reference/items/static-items.html

Constant See: https://doc.rust-lang.org/reference/items/constant-items.html

Static items 與 constant items 很像,除了 static 有確定的的內存地址外(注:constant 是沒有確定的內存地址的)。Static 的特點:

有確定的內存地址

static lifetime

程序結束時不會調用 drop trait

初始化要 constant expression

多線程數據安全

static mut 是 unsafe 的

2.2.1.1. 在函數內部定義 (local)

注:前已有例子,不再重複

2.2.1.2. 直接定義在源代碼文件中(global)

static G_VALUE: i32 = 3;
static mut M_VALUE:u32 = 0;

2.2.1.3. 定義 static 的 String

static G_STRING1:String = String::new();
static G_STRING2:String = "".to_owned();

上面這個代碼是編譯不過的,因爲 String 其實是 Vec,可以修改,需要分配 heap 內存,不具有有常量的性質。而 & str 可以是常量,如下代碼可以工作

static G_STRING:&str = "same string";

是否可以定義 “static name: String”?

不可以,只能定義一個與 String 類似的的包裝,如 “&'static String”(不是 static &String)等。下面是 “&'static String” 的實現:

函數方式 (Cell/UnsafeCell 兩種實現)

pub fn FN_G_STRING2() -> &'static String {
    static one:Once = Once::new();
    static mut data: Cell<Option<String>> = Cell::new(None);
    one.call_once(|| {
        unsafe { data.set(Some("string FN_G_STRING2".to_owned())); }
    });
    unsafe { data.get_mut()}.as_ref().expect("static is not init")
}
pub fn FN_G_STRING3() -> &'static String {
    static one:Once = Once::new();
    static mut data: UnsafeCell<Option<String>> = UnsafeCell::new(None);
    one.call_once(|| {
        unsafe { *data.get() = Some("string FN_G_STRING3".to_owned())};
    });
    unsafe { &*data.get()}.as_ref().expect("static is not init")
}

通用實現

struct StaticData<T>{
    one: Once,
    data: UnsafeCell<Option<T>>,
}
impl<T> StaticData<T>{
    pub const fn new() -> Self {
        StaticData {
            one: Once::new(),
            data: UnsafeCell::new(None),
        }
    }
    pub fn get<F: FnOnce() -> T>(&'static self, builder: F) -> &'static T {
        self.one.call_once(||{
            unsafe { *self.data.get() = Some(builder()); }
        });
        unsafe { &*self.data.get()}.as_ref() .expect("static not init")
    }
}
unsafe impl<T: Sync> Sync for StaticData<T> {}
unsafe impl<T: Sync> Send for StaticData<T> {}
fn demo_static_data() ->&'static String{
    static s_data: StaticData<String> = StaticData::new();
    s_data.get(||"some string".to_owned())
}

static 的實現都是類似的,需要解決如下兩個問題:

注:使用 std::sync::Once 只是爲了寫代碼方便,減少引入 package

2.2.2. 相關的庫

從上面實現 “&'static String”,可以看出,rust 對 static 類型有嚴格的限制,使用起來特別不友好,所以出現了三方庫來解決方便使用的問題。爲什麼官方不直接支持呢?猜的原因:

2.2.2.1.lazy_static

這是 macro 實現,非常小,主要方便做 static 的初始化。它的實現與 “通用實現” 類似,其中只使用了 package spin,沒有使用 std::sync::Once,應該是覺得 std 實現的不夠簡單明確,也或者是性能達不到要求。下面是實現概要代碼:

struct G_STRING1 {
    __private_field: (),
}
#[doc(hidden)]
static G_STRING1: G_STRING1 = G_STRING1 {
    __private_field: (),
};
impl ::lazy_static::__Deref for G_STRING1 {
    type Target = String;
    fn deref(&self) -> &String {
        #[inline(always)]
        fn __static_ref_initialize() -> String {
            {
                String::new()
            }
        }
        #[inline(always)]
        fn __stability() -> &'static String {
            static LAZY: ::lazy_static::lazy::Lazy<String> = ::lazy_static::lazy::Lazy::INIT;
            LAZY.get(__static_ref_initialize)
        }
        __stability()
    }
}
impl ::lazy_static::LazyStatic for G_STRING1 {
    fn initialize(lazy: &Self) {
        let _ = &**lazy;
    }
}

代碼邏輯大概如下:

Lazy_static 的特點:

2.2.2.2.once_cell

功能比 lazy_static 多,沒有使用 macro,其中只使用 parking_lot。有 sync(thread-safe)與 unsync(Not thread safe) 兩個版本。下面是 sync 下概要代碼(由於代碼太多,只列舉 initialize 函數):

/// Safety: synchronizes with store to value via SeqCst read from state,
/// writes value only once because we never get to INCOMPLETE state after a
/// successful write.
#[cold]
pub(crate) fn initialize<F, E>(&self, f: F) -> Result<(), E>
where
    F: FnOnce() -> Result<T, E>,
{
    let mut f = Some(f);
    let mut res: Result<(), E> = Ok(());
    let slot: *mut Option<T> = self.value.get();
    initialize_inner(&self.state_and_queue, &mut || {
        let f = unsafe { take_unchecked(&mut f) };
        match f() {
            Ok(value) => {
                unsafe { *slot = Some(value) };
                true
            }
            Err(err) => {
                res = Err(err);
                false
            }
        }
    });
    res
}

同樣使用到了 Option 及初始化一次(Once),不過 Once 是直接實現,沒有使用庫。Once_cell 的特點:

2.2.2.3.static_init

功能比 once_cell 多,直接依賴的 package 變成 6 個,據說性能比前面兩個都好。提供 “statics dropped at program exit”,就是在程序退出時調用 drop 的功能

Static_init 的特點:

2.2.2.4.std::sync::Once

官方實現沒有 parking_lot 好,rust 官方實現與多線程及 future 相關的內容,實現不是很理想,所以有特別多的非官方實現,更有從 rust 出來,然後自己實現的(如:https://github.com/smol-rs),所以 lazy_static,once_cell,static_init 都沒有使用官方的 Once。

2.2.2.5.parking_lot::Once

非常不錯的關於鎖的實現,也提供了 parking_lot::ReentrantMutex(不過這個功能只是使用方便,理想情況下應該不使用 ReentrantLock,如果使用了,設計多多少少都有一點不合理)的實現。

2.3.“static” 字段

不支持這種語法,帶 class(面嚮對象語言,如 c++,java,dart 等)的語言都有,與 class 的所有實例都共享且是唯一,他不屬於實例,只屬於類。要想達到這個效果可以在 fn 中使用 static 定義一個變量作爲返回值來達到相同的目的。這種 fn_static 方式在方法裏面,非常方便初始化,也很容易理解。實現如下:

pub fn fn_static() -> i32{
    static STATIC_FIELD: i32 = 10;//在第一次調用時分配,多次調用,也不會初始化多次
    return STATIC_FIELD;
}

從使用上來說,這種方法更實用,可能官方也就沒有增加新的語法了。

2.4.“static methods”/associated function

在 rust 中沒有 “static methods” 這種說法,與之一至的是 “associated function”,中文叫關聯方法,就是沒有接收者的方法,即在 trait 或 struct 中,第一個參數不是 self 的方法,在調用時不需要 struct 的實例。這種方法與直接定義的 fn 效果是一樣的,只多了一層“命名空間” 而已。例子如下:

pub struct GlobalData{
    name: String,
}
impl GlobalData{
    pub fn new()-> GlobalData{
        GlobalData{name:"".to_owned()}
    }
}
pub fn GlobalData_new() -> GlobalData{
    GlobalData{name:"".to_owned()}
}

這裏的函數 GlobalData_new 與 GlobalData::new 效果是一樣的,GlobalData::new 更有層次感覺,能表達出他們之間的關係,也更容易閱讀與理解。

這種 associated function 在 trait 中時,不同的 struct 可以實現不同的 “static” 方法,這是那些帶有 class 的語言所不支持的。當然 C++ 中也是不支持的,但是 C++ 有一個類似效果,是 C++ 泛型 / Template 的副作功能實現(see: c++ template static function specialization),比 Rust 實現複雜。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/KtxMP3Y4dWT4bcTBWJhT7A