lazy_static はもう古い!? once_cell を使おう
この記事を 3 行でまとめると
- Rust のグローバル変数には多くの制限があった
- 制限を撤廃し、容易にグローバル変数を使うためのクレートが
lazy_static
-
lazy_static
の代替となるonce_cell
が登場
Rust のグローバル変数には多くの制限があった
Rust にはグローバル変数がありますが、
- 定数でしか初期化できない
- 変更可能にすると
unsafe
を使用する必要がある - 変更不可にするとスレッドセーフな型しか使用できない
という制限があり、非常に使いづらい物となっていました。
例えば、一度だけ巨大なテキストを読み込んでグロバール変数に格納したいとします。
しかし、次のように書くと unsafe
無しではグローバル変数を変更できないため、コンパイルエラーとなってしまいます。
static mut LARGE_TEXT : String = String::new();
fn main() {
LARGE_TEXT = load_large_text();
}
/// 巨大なテキストを読み込む
fn load_large_text() -> String {
todo!()
}
error[E0133]: use of mutable static is unsafe and requires unsafe function or block
--> src\main.rs:3:3
|
3 | LARGE_TEXT = load_large_text();
| ^^^^^^^^^^ use of mutable static
|
= note: mutable statics can be mutated by multiple threads: aliasing violations or data races will cause undefined behavior
unsafe
を使えばコンパイルエラーは無くなりますが、巨大テキストの使用中にうっかりテキストを書き換えてしまう可能性があり、危険です。
static mut LARGE_TEXT: String = String::new();
fn main() {
unsafe {
// 今LARGE_TEXTを使用していないとは限らない、その事をどうか思い出して頂きたい
LARGE_TEXT = load_large_text();
}
}
では RefCell
を使用すれば実行時に使用中かどうかのチェックが入るため、安全になるかと思えば、RefCell
はスレッドセーフではないので使えません。
use std::cell::RefCell;
static LARGE_TEXT: RefCell<String> = RefCell::new(String::new());
fn main() {
*LARGE_TEXT.borrow_mut() = load_large_text();
}
error[E0277]: `std::cell::RefCell<std::string::String>` cannot be shared between threads safely
--> src\main.rs:2:1
|
2 | static LARGE_TEXT: RefCell<String> = RefCell::new(String::new());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `std::cell::RefCell<std::string::String>` cannot be shared between threads safely
|
= help: the trait `std::marker::Sync` is not implemented for `std::cell::RefCell<std::string::String>`
= note: shared static variables must have a type that implements `Sync`
それなら RefCell
のスレッドセーフ版である Mutex
ならば使えるかと言えば、グローバル変数は定数以外で初期化できない為、使えません。
use std::sync::Mutex;
static LARGE_TEXT: Mutex<String> = Mutex::new(String::new());
fn main() {
*LARGE_TEXT.lock().unwrap() = load_large_text();
}
error[E0015]: calls in statics are limited to constant functions, tuple structs and tuple variants
--> src\main.rs:2:36
|
2 | static LARGE_TEXT: Mutex<String> = Mutex::new(String::new());
|
「Rust のグローバル変数って使えない!」
そう思った貴方、正解です。
グローバル変数をそのまま使うのは諦めて、より使いやすくするためのクレートを使いましょう。
lazy_static
制限を撤廃し、容易にグローバル変数を使うためのクレートが lazy_static
を使うと、初回アクセス時に一回だけ初期化処理が実行されるグローバル変数を作る事ができます。
use lazy_static::lazy_static;
lazy_static! {
static ref LARGE_TEXT: String = load_large_text();
}
fn main() {
println!("{}", *LARGE_TEXT);
}
lazy_static
は便利で、あっという間にデファクトスタンダードの地位に上り詰めました。
しかし、この lazy_static
、マクロを使っているため微妙にわかりにくいと思いませんか?
lazy_static
の代替となる once_cell
が登場
そんな中、lazy_static
と同様の処理をマクロ無しで実現した once_cell
が登場しました。
先ほどの lazy_static
の例は once_cell
を使用すると次のように書くことができます。
use once_cell::sync::Lazy;
static LARGE_TEXT: Lazy<String> = Lazy::new(|| load_large_text());
fn main() {
println!("{}", *LARGE_TEXT);
}
マクロを使っていないため、この変数を関数に渡したり構造体のメンバにする、といった応用も可能となっています。
fn may_use_large_text(large_text: &Lazy<String>) {
todo!()
}
struct LargeTextCache {
large_text: Lazy<String>,
}
ダウンロード数はまだ lazy_static
には及ばないものの急成長しており、標準ライブラリ入りも検討されています。
Links
lazy_static
once_cell
Discussion