LoginSignup
40
27

More than 5 years have passed since last update.

Rustとlazy static

Posted at

 やっはろー。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 した際に渡された式で初期化する、といった感じのようです。発想が天才のそれっぽい。

Kimundi / lazy-static.rs - GitHub

40
27
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
40
27