🦀
Rust で mutable なグローバル変数を作る方法
基本的に Rust ではグローバル変数の使用はあまり推奨されていないが、たまには使いたくなることもあるのでメモしておく。
コンストラクタが const な場合
Vec
のようにコンストラクタとなる関数が const fn
である場合、単に Mutex
で包めばよい。
use std::sync::Mutex;
use std::thread;
use std::time::Duration;
// mutable global variable
static CACHE: Mutex<Vec<String>> = Mutex::new(Vec::new());
fn main() {
// writer thread
let t1 = thread::spawn(|| {
let mut cache = CACHE.lock().unwrap();
cache.push("hello".to_owned());
});
// reader thread
let t2 = thread::spawn(|| {
thread::sleep(Duration::from_secs(1));
let cache = CACHE.lock().unwrap();
println!("{:?}", cache.first());
});
t1.join().unwrap();
t2.join().unwrap();
}
コンストラクタが const でない場合
HashMap
のようにコンストラクタが const fn
でない場合、単に Mutex
で包むだけでは値を構築できない。そこで、Mutex
を更に LazyLock で包むことで初期化を遅延させる。
use std::collections::HashMap;
use std::sync::{LazyLock, Mutex};
use std::thread;
use std::time::Duration;
// global mutable variable
static CACHE: LazyLock<Mutex<HashMap<String, String>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
fn main() {
// writer thread
let t1 = thread::spawn(|| {
let mut cache = CACHE.lock().unwrap();
cache.insert("hello".to_owned(), "world".to_owned());
});
// reader thread
let t2 = thread::spawn(|| {
thread::sleep(Duration::from_secs(1));
let cache = CACHE.lock().unwrap();
println!("{:?}", cache.get("hello"));
});
t1.join().unwrap();
t2.join().unwrap();
}
LazyLock は Rust 1.80 で安定化された。それ以前のバージョンの場合は以下に示すような別方法が必要になる。
Rust 1.79 以前の場合
外部crateが必要となるが、LazyLock の代わりに once_cell の Lazy が利用できる。
use std::collections::HashMap;
use std::sync::Mutex;
use std::thread;
use std::time::Duration;
use once_cell::sync::Lazy;
// global mutable variable
static CACHE: Lazy<Mutex<HashMap<String, String>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
pub fn example() {
// writer thread
let t1 = thread::spawn(|| {
let mut cache = CACHE.lock().unwrap();
cache.insert("hello".to_owned(), "world".to_owned());
});
// reader thread
let t2 = thread::spawn(|| {
thread::sleep(Duration::from_secs(1));
let cache = CACHE.lock().unwrap();
println!("{:?}", cache.get("hello"));
});
t1.join().unwrap();
t2.join().unwrap();
}
また、標準ライブラリだけで実装したいのであれば、OnceLock
を使うこともできる。この場合は初期化兼取得用の関数を用意することになる。
use std::collections::HashMap;
use std::sync::{Mutex, OnceLock};
use std::thread;
use std::time::Duration;
// global mutable variable
static CACHE: OnceLock<Mutex<HashMap<String, String>>> = OnceLock::new();
fn get_cache() -> &'static Mutex<HashMap<String, String>> {
CACHE.get_or_init(|| Mutex::new(HashMap::new()))
}
fn main() {
// writer thread
let t1 = thread::spawn(|| {
let mut cache = get_cache().lock().unwrap();
cache.insert("hello".to_owned(), "world".to_owned());
});
// reader thread
let t2 = thread::spawn(|| {
thread::sleep(Duration::from_secs(1));
let cache = get_cache().lock().unwrap();
println!("{:?}", cache.get("hello"));
});
t1.join().unwrap();
t2.join().unwrap();
}
組み込み型の場合
bool
, i64
, usize
などの組み込み型には AtomicBool, AtomicI64, AtomicUsize といった atomic 型が標準ライブラリに用意されている。このような型を使えば mutable なグローバル変数を作れる。
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
use std::time::Duration;
// global mutable variable
static COUNT: AtomicUsize = AtomicUsize::new(0);
fn main() {
// writer thread
let t1 = thread::spawn(|| {
for _ in 0..1000 {
COUNT.fetch_add(1, Ordering::SeqCst);
}
});
// reader thread
let t2 = thread::spawn(|| {
thread::sleep(Duration::from_secs(1));
let count = COUNT.load(Ordering::SeqCst);
println!("{}", count);
});
t1.join().unwrap();
t2.join().unwrap();
}
Ordering は、よくわからない場合は SeqCst
を指定しておくのが無難。
Discussion