やっはろー。lazy-static.rs が便利っぽい、という話をします。
設定ファイルや辞書データなどの適当なファイルをあらかじめ読んでおき、その内容をグローバルに置きたい、という場面はそれなりにあると思います。Rust では、static
でグローバル変数をつくることができますが、初期化に使う式はコンパイル時に評価できるものでなくてならないため、先のような場面では使えません。
// ✔
static SETTING: Setting = Setting { ... };
// ✗
static SETTING: Setting = {
// ファイルを読んだり
// デシリアライズしたり
};
ではどうするかというと、static mut
な変数を置いて、適当な関数から適当なタイミングで初期化することになります。とてもつらい。
というのを解決するのが lazy-static.rs
です。
#[macro_use]
extern crate lazy_static;
lazy_static! {
static ref SETTING: Setting = {
// ファイルを読んだり
// デシリアライズしたり
};
}
fn do_something() {
// 使うときは deref
*SETTING ...
}
実装が気になるので、マクロを覗いてみます。
// 切り貼りしているので、元のコードとは多少異なります
// https://github.com/Kimundi/lazy-static.rs/blob/master/src/lib.rs
// `$N` は変数の名前
// `$T` は変数の型
// `$e` は初期化に使う式
struct $N {__private_field: ()}
static $N: $N = $N {__private_field: ()};
impl ::std::ops::Deref for $N {
type Target = $T;
fn deref<'a>(&'a self) -> &'a $T {
#[inline(always)]
fn __static_ref_initialize() -> Box<$T> { Box::new($e) }
unsafe {
use std::sync::{Once, ONCE_INIT};
use std::mem::transmute;
#[inline(always)]
fn require_sync<T: Sync>(_: &T) { }
static mut DATA: *const $T = 0 as *const $T;
static mut ONCE: Once = ONCE_INIT;
ONCE.call_once(|| {
DATA = transmute::<Box<$T>, *const $T>(__static_ref_initialize());
});
let static_ref = &*DATA;
require_sync(static_ref);
static_ref
}
}
}
Deref
トレイトの実装に static mut
な変数を持ち、初めて deref
した際に渡された式で初期化する、といった感じのようです。発想が天才のそれっぽい。