メモ置き場

メモ置き場です.開発したものや調べたことについて書きます.

[tex: ]

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が自動的に開放され、開放忘れにならなくて済む。