というか完全にfutures-0.3が正式リリースされてると思ったが、そうでもなかったでござる・・・。まだfutures-previewのままなのね。
とりま今日は寝る。
今度こそ寝る。
このエラーバグってやがる・・・。
#rust
@yumetodo これ Result<Result<_, JsValue>, JsValue> のイテレータを collect しようとしているように見えるので、途中の .map() が .and_then() であるべきみたいな話だったりしません?エラーメッセージだけ見た所感ですが。
@lo48576 なるほど、指摘通りでした!ありがとうございます。
しかし依然発生する謎の mismatched types・・・
@yumetodo wasm_bindgen::closure::Closure - Rust
https://docs.rs/wasm-bindgen/0.2.50/wasm_bindgen/closure/struct.Closure.html#method.wrap
どの部分かはわかりませんが、 Rust のクロージャそのものではなく Closure 型を渡してやる必要がありそうですね。 Closure::wrap(Box::new(...)) で Rust のクロージャから Closure 型に変換できそうです
@lo48576 Closure型を要求する場所はわかったのですが、 やっぱりRustコンパイラの気持ちを理解するのは難しいです・・・(Promise.thenに渡した関数からPromise返せないのは
@yumetodo js_sys::Promise - Rust
https://docs.rs/js-sys/0.3.27/js_sys/struct.Promise.html#method.then
Rust というより、 js_sys::Promise::then が受け取るコールバック関数が FnMut(JsValue) であって戻り値があるっぽい雰囲気がないのが不思議ですね…… (Closure の意味をまだ掴みきれてないのでもしかしたら見落としてる何か意味があるのかもですが)
@lo48576 Rustの型システムをいまいち理解していないですが、そういうのは表せないんだと思ってましたがそうでもない・・・?
やっぱりfutures-preview入れて一回変換しないとだめなのかなと思わないでもないので試してみます!
@yumetodo A, B を受け取って C を返すような、状態を持つ関数 (クロージャなど) は、 FnMut(A, B) -> C と表現されるので、 -> のない関数型は戻り値がない (つまり () 型を返す) ものです。
Promise::then は何かの理由があって意図して戻り値を受け入れないようデザインされているのかもしれませんが、ちょっと踏み込んで調べてみないとわからないです……
@lo48576
> -> のない関数型は戻り値がない
そういえばそうですよね・・・。
試しにJsFutureに一度変換してみましたがand_thenがないとか言われて困惑しています。
https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen_futures/
のサンプルと変わらないはずなんだけどなぁ・・・。
@yumetodo これは and_then が Future trait 経由で提供されている関数なので、 Future をどこかで use してやればいけそうな気がします
@lo48576 うーん
https://github.com/rust-lang-nursery/futures-rs/tree/0.3
のREADMEに書いてある
extern crate futures-preview as futures;
が
dash-separated idents are not valid
とか
can't find crate for futures
とか言われて謎で、
extern crate futures;
に書き換えると今度はご覧の有様で、ちょっとどうなっているのか・・・
@yumetodo これドキュメントが extern futures_preview as … の間違いですね。 rust ソース中からはアンダースコアを使う必要があるので
@lo48576 それでもなおcan't find crateなんですよね・・・
@yumetodo
GitHub - rust-lang-nursery/futures-rs: Zero-cost asynchronous programming in Rust
https://github.com/rust-lang-nursery/futures-rs/tree/master
そもそも Cargo.toml で
futures-preview = "=0.3.0-alpha.18"
してたら、あとは (extern crate とかなしに)
use futures::future::Future;
できるっぽいですね (0.3 branch より master branch の方が進んでるのでそっちを見るとよさそう)
@yumetodo というか extern crate は edition 2015 のちょっと古い機能なので、 edition 2018 を使っているなら大抵は extern crate 類まるごと不要ですね
@lo48576 なるほど、そういえばQiitaにそんな紹介記事を見た気がします。
・・・で、やっぱりand_thenないって言われますね、エラーメッセージのwasm_bindgen_futures::legacy_shared::JsFutureって部分が気になっていますが・・・。
@yumetodo わかりました、 wasm-bindgen-futures は futures-0.1 に依存しているので、 futures-0.3 に合わせて使いたいなら compatibility layer を入れてやる必要があります。
以下の記事で簡単な説明があります
Compatibility Layer | Futures-rs
https://rust-lang-nursery.github.io/futures-rs/blog/2019/04/18/compatibility-layer.html
@lo48576 それってfeatures指定しましょうと言うことだと思うんですが、いろいろやってだめでした・・・。
@yumetodo さっきのページの "0.1 futures in async functions" がおそらく該当部分で、 JsFuture が futures 0.1 の Futures なので、これを .compat() で std (0.3) の future にする必要があります (そのために use futures::compat::Future01CompatExt;
しておく必要もあります)
@yumetodo JsFutures::from(p).compat().and_then(...) でいけるんじゃないかなぁ…… (自信なし)
@yumetodo あ、そうか……
.compat() で返ってくるのは std::future::Future なので、ちょっとメソッド類が昔 (0.1) の future と違いますね。
昔は Futures は Result のように Item と Err を持っていたのですが、 std (0.3) の future は単体の Output しか持っていないため、 Result のように振る舞う future 用に別の extension trait が用意されており、 and_then もそちらにあります。
futures::future::TryFutureExt - Rust
https://rust-lang-nursery.github.io/futures-api-docs/0.3.0-alpha.18/futures/future/trait.TryFutureExt.html#method.and_then
@yumetodo 基本的に .compat() で std の future に変換したら、それ以降は futures-preview の 0.3 のドキュメントを見るといいと思います
@lo48576 なんか根本的な問題を引き当てた気がします・・・
@yumetodo これはたぶん future_to_promise が future (0.1) → Promise の関数だからで、すべきことは and_then で返ってきた 0.3 の future を 0.1 のものに変換してやる感じかと
@lo48576 いや、そっちはそうなんですが
the trait core::future::future::Future
is not implemented for js_sys::Promise
のほう・・・
@yumetodo それも結局「0.3 の future が」「js_sys::Promise には」実装されていない (0.1 の future は実装されているはず) という話なので、 0.1 と 0.3 を適切な場所で変換してやればいけそうな気はします。
futures::future::TryFutureExt - Rust
https://rust-lang-nursery.github.io/futures-api-docs/0.3.0-alpha.14/futures/future/trait.TryFutureExt.html#method.compat
これでしょうか
@yumetodo core::future (std::future) というのは Future の基本部分が最近の std に組込まれたもので、 futures 0.3 はこれをベースに作られています。
一方の futures 0.1 の Future は std のものとは別物なので、同じ Future という名前ではありますが、どちらのことを指しているのかはモジュールや型から判断する必要があります
@lo48576 なんとなく見えてきました。明日がんばります。
@lo48576 だめでした。ついでにStackOverflowに投げたらlifetimeが云々言われました・・・・
https://stackoverflow.com/questions/57720248/type-mismatch-resolving-future-to-promise?noredirect=1#comment101894189_57720248
@yumetodo これエラーメッセージが (コンパイラの視点の問題か) 逆で、「future_to_promise() は Future<Item = JsValue, Error = JsValue> を要求しているが、実際に渡されたのは Future<Item = JsString, JsValue> だった」というエラーですね。
wasm_bindgen_futures::future_to_promise - Rust
https://docs.rs/wasm-bindgen-futures/0.3.27/wasm_bindgen_futures/fn.future_to_promise.html
@yumetodo で、当該 get_replaced(...) を get_replaced(...).map(|v| v.into()) などすると JsValue の Future にできるんですが、そうすると今度は「markdown_text: &str の寿命が Promise オブジェクトより長い必要があるので &'static str」にしろと言われますね。
実際 &'static str にするか、 String にするか、 std::borrow::Cow<'static, str> などにする必要がありそうです
@yumetodo あー、でも pulldown_cmark::Parser が &'a str しか受け付けないんですね……
この場合選択肢はふたつあって、
・文字列をヒープにコピーしたのち一時的にリークさせて、それを &'static str であるかのように使う
・owning_ref のような crate で所有権付きオブジェクトへの参照をうまく使う仕組みで String と Parser の両方を持つ
owning_ref - Rust
http://kimundi.github.io/owning-ref-rs/owning_ref/index.html
@lo48576 owning_ref を使うってどういうことかイマイチわかってませんがとりあえず泥縄式ではだめでした
@yumetodo owning_ref ちょっと調べてみましたが今回のケースでは使えなそう……
leak させる方法は、文字列を Box<str> にしたのち Box::leak() に渡してやると、 Box の所有権を捨てて &'static mut str が得られるという魔法があるので、それを使って &'static str を錬成します。
std::boxed::Box - Rust
https://doc.rust-lang.org/stable/std/boxed/struct.Box.html#method.leak
&'static str なら Parser に渡して使えるのでいろいろしたのち、 Promise による計算が終わってから (つまり then とか使うのかな (調べてない)) パーサに渡してあった &'static mut str を Box::from_raw() に渡してやると、再度 Box に戻すことができるので、それでメモリ解放します
@yumetodo つまり一時的にコンパイラが所有権を追跡できない形にするので unsafe になりますし、ミスるとメモリリークとかクラッシュとか UB とかありえます。
でも今回のケースでは (主に Parser の柔軟性が低いせいで) unsafe は避けがたい気がしています……
@yumetodo Future が本質的には状態を持った関数オブジェクトであることに留意すると、 Parser が所有する &str の寿命 (そしてそれがさす文字列本体の寿命も) が Future (Promise) そのものよりも長くないといけないことに納得しやすいかと思います
@yumetodo Promise そのものというか、 Parser よりもでした。 Parser が破棄されたあとは文字列も安全に破棄できるので、その finally 的な処理も future として後ろに付けておくべきみたいな話です
@lo48576 Box::from_rawしようにもborrow chckerに怒られるんですがどうしたら・・・
@yumetodo これは Box::leak() が返した &'mut static str が Parser<'static> に (&'static str として) 借用されているせいですね。
たぶん Box::from_raw() 前に明示的に Parser を破棄して &'mut static str を使う権利を取り戻してやれればいけると思うのですが
@yumetodo それ Drop::drop() が呼ばれてたりしません?念のため std::mem::drop() と明示的に書いて確認してみてもらえますか
@yumetodo いっそ何もかもあきらめてめちゃくちゃポインタ使いまくって &'static mut str と &'static str を共存させるみたいな悪魔のような手を思い付きましたが、それはそれで険しそうなので険しい…… (まあ C++ みたいなものと思えば)
@lo48576 C++的にラッパー関数を噛ませれば解決するの法則を信じて見たけど別の問題ががが
@yumetodo String::from(foo) を foo.to_owned() にしてみたらどうでしょう
@lo48576 あっちが立てばこっちが立たず・・・
@yumetodo そもそもの話になってしまうんですが、 parser を使った処理ってふつう CPU intensive なので future にする必要がなさそうに思うのですが……これってパースすべき markdown を生成する部分とパースして成果物を出す部分とそれを promise にする部分に分割することで、 parser と markdown_text の寿命をもうちょっと制限できないんでしょうか
@lo48576 最終的にパーサーからmarkdownのimg記法のURL抜き出して、ファイル読み出して某所にネットワークに投げつけるみたいのを想定してPromise使っているので分割したところでパーサーはずっと持って回らないと行けない気がします。
@lo48576 その後、そもそも同じテキストを複数回パーサーに通しても同じ解析結果になるよねっ、という大胆な仮定のもと、そもそもパーサーを使い捨てることで、寿命問題に決着をつけたものの、pulldown_cmarkのEventで画像のaltが取れないという根本的な問題ががが・・・
https://github.com/yumetodo/markdown_img_url_editor/pull/13#issuecomment-526926606