Rustでグローバル変数の実現
最近はRustの勉強をしている。 Rustはコンパイル時にいい感じにメモリ管理ができる一方で、C++とかで実現できることが簡単にできなくなっている。
ミュータブルなグローバル変数(とくにシングルトン)を実現したかったので、その方法をメモしておく。
イミュータブルなグローバル変数はstaticで実現できる。
static data: &str = "This is static variable"; fn func1() { println!("func1: {:?}", data); } fn func2() { println!("func2: {:?}", data); } fn main() { func1(); func2(); println!("main: {:?}", data); }
が、static変数はミュータブルにできない、memory allocationができない(vec!とかができない)などと制約が合って不便であった。
グローバルかつミュータブルな変数を作る方法を調べてみると、幾つかやり方があって
rust - How do I create a global, mutable singleton? - Stack Overflow
once_cell
というトレイトを使うのが良さそうだった。once_cell
トレイトはnightlyビルドにSyncLazy
として導入されているので、本家に認められたということでonce_cell
を使った方法を試してみる。
use once_cell::sync::Lazy; // 1.3.1 use std::sync::Mutex; static ARRAY: Lazy<Mutex<Vec<u8>>> = Lazy::new(|| 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()); }
[package] name = "sample" version = "0.1.0" edition = "2021" [dependencies] once_cell = "1.3.1"
実行してみると
$ cargo run
called 3
と、確かにグローバルかつミュータブルな変数が実現できた。
ミソはLazy
で内部可変性をもたせ、更にmutexで保持したい値をラップすることでスレッドセーフに対応させているところ。
また、グローバル変数の値を触る時にはlock
メソッドを呼び出しているため、例えば
fn add_value(val: u8) { ARRAY.lock.unwrap().push(val); }
のように関数経由で値を触るようにしておくと、関数のスコープを抜けたタイミングでlockが自動的に開放され、開放忘れにならなくて済む。