🤖

Rust の関数のローカルスコープで static mut な変数を扱う

2021/12/26に公開1

たまに、ローカルスコープ内で可変な静的変数を宣言して使いたいなーと思うことがあります。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つのやり方がありそうです。それぞれまとめてみます。

  1. unsafe を許容する
  2. std::sync::atomic な型を使う (atomic な型のみ)
  3. 内部可変性を使う (thread_local!RefCell<T>)
  4. 内部可変性を使う (once_cellMutex<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 に関する詳細はこちらを参照。

https://doc.rust-lang.org/std/sync/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! については、こちらを参照ください。

https://doc.rust-jp.rs/book-ja/ch15-05-interior-mutability.html
https://doc.rust-lang.org/std/thread/struct.LocalKey.html

thread_local! を使った内部可変性の実現についてや、static な変数についてはこちらの素晴らしい記事が詳しいです。

https://qiita.com/tatsuya6502/items/bed3702517b36afbdbca#ミュータブルなスレッドローカルデータを-thread_local-マクロと-refcell-で実現する

内部可変性を使う (once_cellMutex<RefCell<T>>)

上記の thread_local! な例は、once_cellMutex<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通りで実現できることがわかりました。

  1. unsafe を許容する
  2. std::sync::atomic な型を使う (atomic な型のみ)
  3. 内部可変性を使う (thread_local!RefCell<T>)
  4. 内部可変性を使う (once_cellMutex<RefCell<T>>)

よりスマートな方法をご存知のかたは、ぜひ教えていただけるとありがたいです。

Discussion