LoginSignup
0
1

More than 3 years have passed since last update.

RustでWebAssembly: spawn_local() へ渡すasync functionに状態を持ったObjectを渡したいときの抜け道

Last updated at Posted at 2020-08-29

はじめに

spawn_local() は wasm-bindgen で、asyncな関数を実行する便利な(JS側を介さない)方法ですが、


pub fn spawn_local<F>(future: F) 
where
    F: Future<Output = ()> + 'static, 

という型になっていて Futureが使う参照は'static なlifetimeをもつものしか渡せません。
つまりこれは、以下のように書くことができないということになります。

impl MyApp {
  fn some_func(&mut self) {
     spawn_local(my_async_process(&mut self)); // &mut selfの lifetimeがstaticではないのでダメ
  }
}

この制約が厄介だなーと思っていたのですが、抜け道を見つけた(いや、最初からそこにあったのでしょうが)のでメモしておきます。Rust初心者にはちょっと気が付きにくいなぁと思ったので。

抜け道

'static MyApp を用意しておく

これは 前回 考えた方法で、まあ確かにこれならうまくいきます。
しかし、my_async_process の中に入れたいのが staticにできるものとは限りません。複数あるObjectとかだとこの方法は使いにくいです。(もちろん、なんとかはできますが)。

Rc<RefCell<MyApp>> を渡す

参照(&)を渡すことはできないですが、 Rc構造体を渡すことはできるようです。
なので、spawn_localしたい部分に、いつもの Rc<RefCell<MyApp>> みたいなのを持ち込むことがでていれば渡すことができます。
Localで作成した普通のObjectならこの方法が良いかもしれないです。Box<HogeHoge> とかでも良いような気がします。

// 仮にこれをselfが持っていれば渡せることになる
let x: Rc<RefCell<MyApp>> = Rc::new(RefCell::new(MyApp::new()));
spawn_local(my_async_process_with_ref_cell(x.clone(), self.clicks));

...

pub async fn my_async_process_with_ref_cell(my_app: Rc<RefCell<MyApp>>, param: u32) {
    // 何か await とかしたりできる(fetchとか)
    // spawn_local() で使えるのは static な 参照しかないが、 Rcに包まれたものは有効。
    my_app.borrow_mut().async_count += param;
}

Raw Pointerを渡す

Rustには 「参照(&, &mut)」とは別に「RawPointer(*)」というのがあります(というのを思い出した)。
https://doc.rust-lang.org/reference/types/pointer.html

「参照」の方は lifetime が管理されていたり、&mut が排他的な扱いをうけたりしていてRustの特別な感じを醸し出していますが、「RawPointer」にはそういうのがありません。代わりに Dereferenceするときは NULLかもしれないし、もう値がないかもしれないので unsafe {} で囲わないといけないことになっています。で、 spawn_local は 「'static ではない参照」を禁じているのであって、RawPointerについては何も言及していません。なので、実はRawPointerを渡すことができるようです。

つまり、こんな抜け道がありました。んー気が付かなかった。

spawn_local(my_async_process_with_raw_pointer(
    self as *mut MyApp,
    self.clicks,
));

...

pub async fn my_async_process_with_raw_pointer(my_app: *mut MyApp, param: u32) {
    // *mut MyApp は 「参照」ではなく 「RawPointer」
    // spawn_local() で使える「参照」は static な 参照しかないが、 Raw Pointerなら渡せる!!
    unsafe {
        (*my_app).async_count += param;
    }
}

さいごに

まあ、どの方法もケースバイケースで使い分けると良さそうです。
いろいろやり方はあるものですね。

0
1
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
0
1