LoginSignup
4
3

More than 3 years have passed since last update.

Rustでグローバルかつ可変のデータを扱う

Posted at

Rustで可変のグローバル変数を使おうとすると意外にややこしい。Cの感覚で導入しようとして苦戦したので、簡単にまとめておく。
ここではシングルスレッド向けのものだけを考え、マルチスレッド向けの話はしない。調べるにあたり、こちらの記事こちらの記事を参考にさせて頂いた。リンク先のほうが説明も詳しいし、マルチスレッドについても言及されているので、より進んだ理解を得たい人はそちらを参照してほしい。

static mutを使う方法(非推奨)

static mut NUMS: Vec<i32> = Vec::new();

fn main() {
    unsafe {
        NUMS.push(1);
        NUMS.push(2);
        assert_eq!(Some(&1), NUMS.get(0));
        assert_eq!(Some(&2), NUMS.get(1));
    }   
}

まず一番最初に思いつくのがこの方法だと思う。
この場合、データを操作したりするにはunsafeブロックで囲う必要がある。unsafeで囲わないと、mutable statics can be mutated by multiple threadsというメッセージ付きでエラーになる。マルチスレッドでの安全性が保証できないよ、ということだ。
じゃあシングルスレッドでしか使わないなら問題ないのだろうか、と思わなくもないが、少なくとも自分にはそのリスクを明確に説明できないし、コンパイラによるチェックの恩恵も得られなくなってしまうので、極力避けるべきだろう。

RefCellを使う方法

use std::cell::RefCell;

thread_local!(
    static NUMS: RefCell<Vec<i32>> = { 
        let v: Vec<i32> = Vec::new();
        RefCell::new(v)
    }   
);

fn main() {
    NUMS.with(|v| {
        v.borrow_mut().push(1);
        v.borrow_mut().push(2);
        assert_eq!(Some(&1), v.borrow().get(0));
        assert_eq!(Some(&2), v.borrow().get(1));
    })  
}

この方法がCとかのグローバル変数の感覚に一番近い気がする。
thread_local!の中で値を宣言し、操作はwithの中で行うことで、unsafeで囲うことなくグローバルなデータを扱うことができる。操作するときにborrowもしくはborrow_mutが必要なことに注意。
thread_local!という名前からもなんとなく察せられるように、スレッドごとに値を保持するため、マルチスレッド環境の場合には別の考慮が必要になる。

RefCellとRcを使う方法

use std::cell::RefCell;
use std::rc::Rc;

thread_local!(
    static NUMS: Rc<RefCell<Vec<i32>>> = { 
        let v: Vec<i32> = Vec::new();
        Rc::new(RefCell::new(v))
    }   
);

fn main() {
    let v = NUMS.with(|v| v.clone());
    v.borrow_mut().push(1);
    v.borrow_mut().push(2);
    assert_eq!(Some(&1), v.borrow().get(0));
    assert_eq!(Some(&2), v.borrow().get(1));
}

RefCellだけを使った場合、withの中に処理を書かなければいけないのでネストが深くなったり、少し見づらいと感じるかもしれない。そんなときは、Rcを使うことでwithの外に変数を持ち出し、ネストを1段階減らすことができる。一度持ち出してしまえば、あとはwithの中で書いたのと同じように操作できる。
ただし、Rcは1つのデータを複数で所有するようなときに使われるため、Rcの使われ方としては少し直感的でないのかもしれないし、withで囲われていたほうがグローバルな変数を使っているという目印にもなるかもしれないので、このあたりは好みなんだろうと思う。
あと、詳しいことは検証していないが、Rcを使う分のオーバーヘッドなども発生しているかもしれない。

まとめ

  • static mutは危険なので極力使わないほうがいい。
  • thread_local!とRefCellを組み合わせることで、安全に扱うことができる。
  • さらにRcを組み合わせると、少しすっきりと書くことができる。
4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3