質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
マクロ

定義された処理手続きに応じて、どのような一連の処理を行うのかを特定させるルールをマクロと呼びます。

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

Q&A

解決済

1回答

1436閲覧

[Rust]自作マクロ定義の中で外部クレートを使う場合、別のプロジェクトからそのマクロを使っても、再度同じ外部クレートの依存関係をCargo.tomlに記述しなくて済むような方法がわからない

akira_kano1101

総合スコア25

マクロ

定義された処理手続きに応じて、どのような一連の処理を行うのかを特定させるルールをマクロと呼びます。

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

2グッド

0クリップ

投稿2022/12/19 04:40

編集2022/12/21 11:56

前提

Rustでの質問で、前提の話になります。

ブラウザ上で動作するwasmに、wasm_bindgen_futuresクレートの関数spawn_localを使っています。

非同期処理(asyncawait)を要求するメソッドをasyncブロックに仕込んでこの関数に引数で与えたいとした場合に起きた話です。

ライフタイムの短い参照はasyncブロック内に入れられません。

例えばeguiクレートの頻出のパラメータui: &mut Uiasyncブロック内部に入れようと思って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の修正で済むのか、どう修正すると解決できるのかがわかりません。

この現象の解決方法をご存知の方、もしいらっしゃいましたらご教示願えますと幸いです。

よろしくお願いします。

holly, termoshtt👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

再エクスポートの形式を取ればよいようです。 とりあえず差分の形で提示します。

diff

1diff --git a/src/lib.rs b/src/lib.rs 2index 7709dfa..1f37adf 100644 3--- a/src/lib.rs 4+++ b/src/lib.rs 5@@ -1,24 +1,26 @@ 6+pub use once_cell::sync::OnceCell; 7+pub use tokio::sync::Mutex; 8+pub use wasm_bindgen_futures::spawn_local; 9+ 10 #[macro_export] 11 macro_rules! build_perform { 12 ($space:ident, $key:ty, $value:ty) => { 13 mod $space { 14 pub mod perform { 15- use once_cell::sync::OnceCell; 16 use std::collections::HashMap; 17 use std::future::Future; 18 use std::hash::Hash; 19- use tokio::sync::Mutex; 20- use wasm_bindgen_futures::spawn_local; 21- static STORE: OnceCell<Mutex<HashMap<$key, $value>>> = OnceCell::new(); 22 23- fn global_data() -> &'static Mutex<HashMap<$key, $value>> 24+ static STORE: $crate::OnceCell<$crate::Mutex<HashMap<$key, $value>>> = $crate::OnceCell::new(); 25+ 26+ fn global_data() -> &'static $crate::Mutex<HashMap<$key, $value>> 27 where 28 $key: Hash, 29 $value: Default, 30 { 31 STORE.get_or_init(|| { 32 let hash_map = HashMap::new(); 33- Mutex::new(hash_map) 34+ $crate::Mutex::new(hash_map) 35 }) 36 } 37 38@@ -66,7 +68,7 @@ macro_rules! build_perform { 39 where 40 Fut: Future<Output = $value> + 'static, 41 { 42- spawn_local(async move { 43+ $crate::spawn_local(async move { 44 let value = f.await; 45 lock_and_do_mut(|hash_map| hash_map.insert(key, value)).await; 46 });

こうすることで OnceCell などは形式的には perform_wasm に属する名前になるので依存先として perform_wasm を書いておけば (perform_wasm を経由する形で) 必要な名前解決は行われます。

投稿2022/12/21 04:24

SaitoAtsushi

総合スコア5444

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

akira_kano1101

2022/12/21 11:51

SaitoAtsushi様 ご回答ありがとうございます。 なるほど、`$crate`で、思う形が作れるんですね! これで開発が進みます。非常に助かりました。 また機会がありましたら、よろしくお願いします。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問