Rust の関数のローカルスコープで static mut な変数を扱う
たまに、ローカルスコープ内で可変な静的変数を宣言して使いたいなーと思うことがあります。C++ で書くとこういうことです。
#include <iostream>
void count_local_static() {
static uint32_t n {5};
std::cout << n++ << std::endl;
}
int main() {
count_local_static();
count_local_static();
count_local_static();
}
// 5
// 6
// 7
Rust にそのまま移植すると起きる問題と解決策
これを普通に Rust で書こうとすると、static mut
にアクセスするのは unsafe
だ、というエラーが起きます。
fn count_local_static() {
static mut COUNT: u32 = 5;
println!("{}", COUNT);
COUNT += 1;
}
fn main() {
count_local_static();
count_local_static();
count_local_static();
}
error[E0133]: use of mutable static is unsafe and requires unsafe function or block
--> src\main.rs:3:20
|
3 | println!("{}", COUNT);
| ^^^^^ use of mutable static
|
= note: mutable statics can be mutated by multiple threads: aliasing violations or data races will cause undefined behavior
error[E0133]: use of mutable static is unsafe and requires unsafe function or block
--> src\main.rs:4:5
|
4 | COUNT += 1;
| ^^^^^^^^^^ use of mutable static
|
= note: mutable statics can be mutated by multiple threads: aliasing violations or data races will cause undefined behavior
これを避けるには、ひとまず下記の4つのやり方がありそうです。それぞれまとめてみます。
-
unsafe
を許容する -
std::sync::atomic
な型を使う (atomic
な型のみ) - 内部可変性を使う (
thread_local!
とRefCell<T>
) - 内部可変性を使う (
once_cell
とMutex<RefCell<T>>
)
unsafe
を使うことを許容する
unsafe
を使うことをいとわなければ、下記のようにシンプルに書くことが出来ます。関数ローカルでしか使わない前提で、unsafe
を使うことに抵抗がなければ、これで十分かもしれません。
fn count_local_static() {
static mut COUNT: u32 = 5;
println!("{}", unsafe { COUNT });
unsafe {
COUNT += 1;
}
}
fn main() {
count_local_static();
count_local_static();
count_local_static();
}
std::sync::atomic
な型を使用する
もし目的とする型が atomic
な型であれば、unsafe
を使わずに書きのように書くことができます。
use std::sync::atomic::{AtomicU32, Ordering};
fn count_local_static() {
static COUNT: AtomicU32 = AtomicU32::new(5);
println!("{}", COUNT.fetch_add(1, Ordering::SeqCst));
}
fn main() {
count_local_static();
count_local_static();
count_local_static();
}
atomic
に関する詳細はこちらを参照。
thread_local!
と RefCell<T>
)
内部可変性を使う (atomic
でない型を unsafe
を使わずに関数ローカルで使いたい場合、thread_local!
でスレッドローカルな変数を関数内で定義して使うのが一つのやり方です。ただし、ミュータブルに使用するためには RefCell
を使用して、内部可変性を利用する必要があります。
use std::cell::RefCell;
fn count_local_static() {
thread_local!(
pub static COUNT: RefCell<u32> = RefCell::new(5u32)
);
COUNT.with(|count| {
let mut c = count.borrow_mut();
println!("{}", *c);
*c += 1;
})
}
fn main() {
count_local_static();
count_local_static();
count_local_static();
}
内部可変性や thread_local!
については、こちらを参照ください。
thread_local!
を使った内部可変性の実現についてや、static
な変数についてはこちらの素晴らしい記事が詳しいです。
once_cell
と Mutex<RefCell<T>>
)
内部可変性を使う (上記の thread_local!
な例は、once_cell
と Mutex<RefCell<T>>
などを使っても代替できます。Sync
が要求されるため、関数ローカルなのに Mutex
が必要になってしまいますが、見た目は多少マシになるかもしれません。
use once_cell::sync::Lazy;
use std::cell::RefCell;
use std::sync::Mutex;
fn count_local_static() {
static COUNT: Lazy<Mutex<RefCell<u32>>> = Lazy::new(|| Mutex::new(RefCell::new(5u32)));
let c_mtx = COUNT.lock().unwrap();
let mut c = c_mtx.borrow_mut();
println!("{}", *c);
*c += 1;
}
fn main() {
count_local_static();
count_local_static();
count_local_static();
}
まとめ
Rust で関数ローカルスコープな static mut
変数を扱う方法をいろいろ試してみました。今回は下記の4通りで実現できることがわかりました。
-
unsafe
を許容する -
std::sync::atomic
な型を使う (atomic
な型のみ) - 内部可変性を使う (
thread_local!
とRefCell<T>
) - 内部可変性を使う (
once_cell
とMutex<RefCell<T>>
)
よりスマートな方法をご存知のかたは、ぜひ教えていただけるとありがたいです。
Discussion
最近Rustの勉強を始めたばかりでとても参考になりました。
4つ目の例は
RefCell
を抜きにしてonce_cell
とMutex<T>
で事足りるのではないかと思いました。