Q&A
前提
Rustでの質問で、前提の話になります。
ブラウザ上で動作するwasm
に、wasm_bindgen_futures
クレートの関数spawn_local
を使っています。
非同期処理(async
、await
)を要求するメソッドをasync
ブロックに仕込んでこの関数に引数で与えたいとした場合に起きた話です。
ライフタイムの短い参照はasync
ブロック内に入れられません。
例えばegui
クレートの頻出のパラメータui: &mut Ui
をasync
ブロック内部に入れようと思ってui.label()
などを使ってreqwest::get()
の結果の値を非同期にスクリーンに表示したいと考えた場合など、そういうときにライフタイムの問題が言われ、コンパイルできません。
wasm
で典型的なパターンの問題と思われます。
rust
1fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { 2 egui::TopBottomPanel::top("select_tab").show(ctx, |ui| { 3 wasm_bindgen_futures::spawn_local(async { 4 let ip = reqwest::get("http://httpbin.org/ip") 5 .await 6 .unwrap() 7 .text() 8 .await 9 .unwrap(); 10 // ui.label(ip); // 'staticなFuture内部にライフタイムの短い参照uiを入れられない 11 }); 12 }); 13}
解決策として次のように考えました。
rust
1build_perform!(ip, i32, String); 2 3fn display(&self, ui: &mut Ui) -> Option<()> { 4 let f = async { 5 reqwest::get("http://httpbin.org/ip") 6 .await 7 .unwrap() 8 .text() 9 .await 10 .unwrap() 11 }; 12 ip::perform::set_begin(1, f); 13 let ip = ip::perform::try_fetch(1)?; 14 ui.label(ip); 15 Some(()) 16}
Future
トレイト内で非同期処理を行なった結果の値を外に出そうと考えました。
この関数は普通に使うには、与えられる引数のトレイト境界条件がFuture<Output = ()> + 'static
であることにより戻り値が()
であることもあって、簡単にasync
ブロックから外に値を取り出せないところあるためが使いづらいです。
これを解決しようとしてみています。
本題
創作したマクロのGitHubはこちらになります。
https://github.com/kano1101/perform-wasm/tree/3e4a68f0135baa8b5873826685ee810a314c993b
また、創作したマクロを使うコードもGitHubに保存しました。
https://github.com/kano1101/reexport/tree/1d1cb4651e92393a78235301f3c2acf3a436384f
今回は、これらのコードを使いたい場合に設定する「依存関係」についてが解決したい問題です。
実現したいこと
創作したマクロのプロジェクトはperform_wasm
と名付けました。
perform_wasm
中のマクロ内では、以下のクレートのモジュールをuseしています。
once_cell
tokio
wasm_bindgen_futures
今の状況では、新たなプロジェクトでこのマクロを使用したい場合、上にリストした3つのクレートをそのプロジェクトでふたたび同じようにCargo.toml
に追加する必要があります。これが問題です!
perform_wasm
を新たなプロジェクトが使うたびに毎回、同じ依存関係を記述しなければならないのが不便なのでこれをしなくてもコンパイルできるようにというのが今回実現したいことです。
GitHubでも確認いただけますが参考までにマクロのコードを掲載します。
perform_wasm/src/lib.rs
rust
1 #[macro_export] 2 macro_rules! build_perform { 3 ($space:ident, $key:ty, $value:ty) => { 4 mod $space { 5 pub mod perform { 6 use once_cell::sync::OnceCell; 7 use std::collections::HashMap; 8 use std::future::Future; 9 use std::hash::Hash; 10 use tokio::sync::Mutex; 11 use wasm_bindgen_futures::spawn_local; 12 static STORE: OnceCell<Mutex<HashMap<$key, $value>>> = OnceCell::new(); 13 14 fn global_data() -> &'static Mutex<HashMap<$key, $value>> 15 where 16 $key: Hash, 17 $value: Default, 18 { 19 STORE.get_or_init(|| { 20 let hash_map = HashMap::new(); 21 Mutex::new(hash_map) 22 }) 23 } 24 25 async fn lock_and_do_mut<F>(f: F) -> Option<$value> 26 where 27 F: FnOnce(&mut HashMap<$key, $value>) -> Option<$value>, 28 $key: Hash, 29 { 30 let mut hash_map = global_data().lock().await; 31 f(&mut *hash_map) 32 } 33 34 async fn lock_and_do<F>(f: F) -> Option<$value> 35 where 36 F: Fn(&HashMap<$key, $value>) -> Option<$value>, 37 { 38 let hash_map = global_data().lock().await; 39 f(&*hash_map) 40 } 41 #[allow(dead_code)] 42 fn try_lock_and_do<F>(f: F) -> Option<$value> 43 where 44 F: Fn(&HashMap<$key, $value>) -> Option<$value>, 45 { 46 let hash_map = global_data().try_lock().ok()?; 47 f(&*hash_map) 48 } 49 50 fn get_and_clone(key: $key, hash_map: &HashMap<$key, $value>) -> Option<$value> 51 where 52 $value: Clone, 53 { 54 hash_map.get(&key).map(|v| v.clone()) 55 } 56 57 pub async fn set_async<Fut>(key: $key, f: Fut) 58 where 59 Fut: Future<Output = $value> + 'static, 60 { 61 let value = f.await; 62 lock_and_do_mut(|hash_map| hash_map.insert(key, value)).await; 63 } 64 #[allow(dead_code)] 65= pub fn set_begin<Fut>(key: $key, f: Fut) 66 where 67 Fut: Future<Output = $value> + 'static, 68 { 69 spawn_local(async move { 70 let value = f.await; 71 lock_and_do_mut(|hash_map| hash_map.insert(key, value)).await; 72 }); 73 } 74 pub async fn fetch_async(key: $key) -> Option<$value> { 75 lock_and_do(|hash_map| get_and_clone(key, hash_map)).await 76 } 77 #[allow(dead_code)] 78 pub fn try_fetch(key: $key) -> Option<$value> { 79 let value = try_lock_and_do(|hash_map| get_and_clone(key, hash_map))?; 80 Some(value) 81 } 82 } 83 } 84 }; 85 }
発生している問題・エラーメッセージ
perform_wasm
を利用する側の別の新たなプロジェクトにもCargo.toml
の依存関係に前述でリストした3つの依存クレートを含めなければコンパイルが通りません。
console
1error[E0433]: failed to resolve: use of undeclared crate or module `once_cell` 2 --> src/main.rs:11:5 3 | 411 | perform_wasm::build_perform!(namespace, i32, String); 5 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ use of undeclared crate or module `once_cell` 6 | 7 = note: this error originates in the macro `perform_wasm::build_perform` (in Nightly builds, run with -Z macro-backtrace for more info) 8 9error[E0433]: failed to resolve: use of undeclared crate or module `tokio` 10 --> src/main.rs:11:5 11 | 1211 | perform_wasm::build_perform!(namespace, i32, String); 13 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ use of undeclared crate or module `tokio` 14 | 15 = note: this error originates in the macro `perform_wasm::build_perform` (in Nightly builds, run with -Z macro-backtrace for more info) 16 17error[E0432]: unresolved import `wasm_bindgen_futures` 18 --> src/main.rs:11:5 19 | 2011 | perform_wasm::build_perform!(namespace, i32, String); 21 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ use of undeclared crate or module `wasm_bindgen_futures` 22 | 23 = note: this error originates in the macro `perform_wasm::build_perform` (in Nightly builds, run with -Z macro-backtrace for more info) 24 25Some errors have detailed explanations: E0432, E0433. 26For more information about an error, try `rustc --explain E0432`. 27error: could not compile `reexport` due to 3 previous errors 28Serving HTTP on :: port 8080 (http://[::]:8080/) ...
逆に、含めればコンパイルは通ります。
該当のソースコード
reexport/Cargo.toml
toml
1[package] 2name = "reexport" 3version = "0.1.0" 4edition = "2021" 5 6# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 8[dependencies] 9console_error_panic_hook = "0.1.7" 10log = "0.4.17" 11reqwest = "0.11.13" 12wasm-logger = "0.2.0" 13 14perform_wasm = { git = "https://github.com/kano1101/perform-wasm.git", branch = "main" } 15# 以下のコメントアウトを外すとちゃんと動作する! 16# once_cell = "1.16.0" 17# tokio = { version = "1.23.0", default-features = false, features = ["sync"] } 18# wasm-bindgen-futures = "0.4.33"
reexport/src/main.rs
rust
1fn main() { 2 console_error_panic_hook::set_once(); 3 wasm_logger::init(wasm_logger::Config::default()); 4 5 log::trace!("some trace log"); 6 log::debug!("some debug log"); 7 log::info!("some info log"); 8 log::warn!("some warn log"); 9 log::error!("some error log"); 10 11 perform_wasm::build_perform!(namespace, i32, String); 12}
試したこと
reexport/run.sh
bash
1( 2 cd `dirname $0` 3 cargo build --target wasm32-unknown-unknown 4 wasm-bindgen ./target/wasm32-unknown-unknown/debug/$(basename `pwd`).wasm --out-dir . --web 5 python -m http.server 8080 6)
実行したコマンド
console
1% pwd 2/Users/a.kano/development/temp/reexport 3% ./run.sh
補足情報(FW/ツールのバージョンなど)
console
1% zsh --version 2zsh 5.8.1 (x86_64-apple-darwin22.0) 3% python --version 4Python 3.11.0 5% wasm-pack --version 6wasm-pack 0.10.3 7% wasm-bindgen --version 8wasm-bindgen 0.2.83 9% cargo --version 10cargo 1.65.0 (4bc8f24d3 2022-10-20)
論点を戻すと、毎回依存クレートをdependenciesに記述すれば動作はしますので、それで解決ということにもしても良いところかもしれませんが、プロジェクトを作るたびに余分なクレートを依存関係に含めなければならないとなると、せっかく良い機能をクレートとしてブラックボックス化しているのに意味がないと思います。
perform_wasm
に修正を入れなければならないのか、reexport
の修正で済むのか、どう修正すると解決できるのかがわかりません。
この現象の解決方法をご存知の方、もしいらっしゃいましたらご教示願えますと幸いです。
よろしくお願いします。
回答1件
あなたの回答
tips
プレビュー
下記のような回答は推奨されていません。
このような回答には修正を依頼しましょう。
2022/12/21 11:51