Rust
futures

task_local! を使う

Rust ではプログラム内でデータを共有するための手段として,グローバル変数に対しては lazy_static! ,スレッドローカル変数には thread_local! というヘルパーマクロが提供されている(これらのマクロの使用例はこちらの記事などを参照)。

futures では,これらのマクロに似たインタフェースを持つ task_local! というマクロが提供されている。これを用いることで,タスク1毎に独立した共有変数を扱うことが出来るようになっている。

futures-0.1 の場合

現行の最新安定板である futures-0.1 では,基本的に thread_local! と同じ見た目になっている。LocalKey::with で取得できるのが共有参照であるため,変数の値を書き換えたい場合は CellRefCell を用いて内部可変性を持たせる必要がある。

task_local!(static NUMBERS: RefCell<Vec<u32>> = RefCell::new(vec![]));

struct B;

impl Future for B {
    type Item = u32;
    type Error = ();

    fn poll(&mut self) -> Poll<u32, ()> {
        NUMBERS.with(|numbers: &RefCell<Vec<u32>>| {
            let last = n.borrow().last().cloned();
            last.map(Async::Ready).ok_or(())
        })
    }
}

thread_local! と同様,RefCell を用いる場合には実行時エラーが生じないよう注意する必要がある。

struct A(B);

impl Future for A {
    type Item = u32;
    type Error = ();

    fn poll(&mut self) -> Poll<u32, ()> {
        NUMBERS.with(|numbers| {
            // ここで返される RefMut はブロック末尾まで生きている
            let numbers = numbers.borrow_mut();

            // そのため,B::poll 内で Ref を取得しようとして実行時エラーになる
            let f = self.0.poll();

            numbers.push(42);
            f
        })
    }
}

futures-0.2 の場合

現在ベータ版まで公開されている futures-0.2 では,Future::poll がタスクの実行コンテキストへの参照を明示的に受け取るように変更されている。これに伴い,共有変数の可変参照が直接取得できるよう LocalKey の API が変更されている。具体的には,LocalKey::with が削除され代わりに LocalKey::get_mut というメソッドが追加される。

// ここで内部可変性を考慮する必要はない
task_local!(static NUMBERS: Vec<u32> = vec![]);

struct B;

impl Future for B {
    type Item = u32;
    type Error = ();

    fn poll(&mut self, ctx: &mut task::Context) -> Poll<u32, ()> {
        // task::Context 内の LocalMap から可変参照を直接取得できる
        let numbers: &mut Vec<u32> = NUMBERS.get_mut(ctx);
        let last = numbers.last().cloned();
        last.map(Async::Ready).ok_or(())
    }
}

futures-0.1 の場合とは異なり,借用ルールに関するエラーは「コンパイル時に」検出されるようになる。また,RefCell によるオーバヘッドがなくなるという利点もある。

struct A(B);

impl Future for A {
    type Item = u32;
    type Error = ();

    fn poll(&mut self, ctx: &mut task::Context) -> Poll<u32, ()> {
        let numbers = NUMBERS.get_mut(ctx);

        // numbers が生きているので違法(コンパイルエラーになる)
        let f = self.0.poll(ctx);

        numbers.push(42);
        f
    }
}

(追記: 2018-04-03T20:06) サンプルコードの修正


  1. 直観的に言うと Executor::execute により構築される Future に紐づいた処理の実行単位