最近 rust を勉強し始めたので、オフラインリアルタイムどう書くで一番簡単な問題(と思っている) サイコロを転がす を解いた。破壊的更新を戦略的に使えるのは、この程度の難易度のサンプルであれば書いてて大変気持ちがいい。
rust の perl-xs を触る
rust の perl-xs なるリポジトリを見つけたので触ってみた。 Perl の carton
と rust の cargo
が動く環境1であれば、 README に書かれている通りリポジトリを clone してきて以下で簡単に試せる。
$ carton install $ carton exec -- 'cd t && perl Makefile.PL && make test'
perl-xs
は Perl API を rust から使いやすいようにラップしたものという位置づけになる。 Perl API への低レベルなバインディングは perl-sys で提供され、 Rust のコードのビルドは Module::Install::Rust で提供される。また、 perl-sys で C のマクロに相当する処理の rust のバインディングを提供するために、内部で Ouroboros を使っている。
perl-xs
を使うと、 XS や C を書かなくても rust だけで Perl のモジュールが書ける。ただし、このクレートは発展途上であり、足りない処理も多い。例えば、今日現在のバージョンでは Perl の values
に相当する処理がないので、作ってみる。
src/hash.rs
に以下のようにイテレータを実装する。
impl HV { ... #[inline] pub fn iter_values(&self) -> IterHVVal { IterHVVal::new(self) } ... } pub struct IterHVVal<'a>(&'a HV); impl<'a> IterHVVal<'a> { fn new(hv: &'a HV) -> Self { unsafe { hv.pthx().hv_iterinit(hv.as_ptr()) }; IterHVVal(hv) } } impl<'a> Iterator for IterHVVal<'a> { type Item = SV; fn next(&mut self) -> Option<Self::Item> { unsafe { let hv = &self.0; let pthx = hv.pthx(); let hv_ptr = hv.as_ptr(); let he = pthx.hv_iternext(hv_ptr); if he.is_null() { None } else { let sv = pthx.hv_iterval(hv_ptr, he); Some(SV::from_sv(pthx, sv)) } } } }
そして、これを利用して Perl のモジュールを書く。以下のような lib.rs
を書いた。ハッシュの値の二乗の和を取るだけのシンプルな関数である。
#[macro_use] extern crate perl_xs; #[macro_use] extern crate perl_sys; mod xstest { use perl_xs::raw::NV; use perl_xs::HV; xs! { package XSTest; sub sum_values(ctx, hv: HV) { let n: NV = hv.iter_values().map(|sv| { let n = sv.nv(); n * n }).sum(); ctx.new_sv(n) } } } xs! { bootstrap boot_XSTest; use xstest; }
そして、 t/
ディレクトリを参考に Makefile.PL
と lib/XSTest.pm
を置けばそれだけで完成。なんともお手軽である。
$ carton exec -- 'cd my_example && perl Makefile.PL && make && perl -Mblib -E "use XSTest; say XSTest::sum_values({a => 1.0, b => 2.0, c => 3.0})"' ... 14
さて、 perl-xs
クレートはこのように大変お手軽でいいのだが、 作者のスライド でも触れられているように残念ながらパフォーマンスに問題がある。手元でもベンチマークを取ってみたが、先程の sum_values
は Pure Perl と比べて 30% しか性能を改善できなかった 2 。同じ処理を素の XS で書いたところ 350% 性能が改善したので、ほんとに遅いとしか言いようがない。
原因を探るべく、 perl-sys 側のコードを見てみると、 Perl の API は JMPENV_*
でラップされていた。以下はビルド中に自動生成された perl_sys.c
からの抜粋である。
int perl_sys_hv_iternext(HE** RETVAL, HV * hv) { int rc = 0; dJMPENV; JMPENV_PUSH(rc); if (rc == 0) { *RETVAL = hv_iternext(hv); } JMPENV_POP; return rc; }
これらを取り除いたところ、 Pure Perl と比べて 100% 性能が改善するようになった。他、余計なコピー処理などを省いて 200% 程度まで改善できたが、キリがないのでそこで辞めておいた。徹底的に省けば、素の XS と同程度まで改善できるだろう。
なお、イテレータを loop
に変えると 20% 程度性能が改善したが、この程度の違いで済むのはさすが rust だなと思った。
トレイト境界の F: FnMut(u8) -> bool という記法について
rust で where
の中でトレイト境界を書ける。そして、クロージャはトレイトで実現されている。ってことなんだけど、クロージャを表す型変数のトレイト境界の書き方はなんとも気持ちが悪い。
struct Cacher<T> where T: Fn(u32) -> u32 { calculation: T, value: Option<u32>, }
Closures: Anonymous Functions that Can Capture Their Environment - The Rust Programming Language
トレイト境界と言えば T: Display + Clone
とか、ジェネリックであれば <>
を使って T: AsRef<str>
みたいな記法になるはずだが、 ()
とか ->
とはなんなのか。
答えから言うと、これは Fn
ファミリー独自の記法のようだ。文法的にはこうなっている。 ->
と ()
を使う規則は TypePathFn
と命名されている。
TypePathFn : ( TypePathFnInputs? ) (-> Type)? TypePathFnInputs : Type (, Type)* ,?
これは Foo<(...), Output=B>
の糖衣だとする文章も残っている。
rust-rfcs/0587-fn-return-should-be-an-associated-type.md at master · nox/rust-rfcs · GitHub
ここまで来ると、ああ、やっぱりクロージャはトレイトなんだなという思いになってくるが、現行ではこの規則で脱糖してもコンパイルすることはできず、 TypePathFn
の記法を使わねばならない。
error[E0658]: the precise format of `Fn`-family traits' type parameters is subject to change. Use parenthetical notation (Fn(Foo, Bar) -> Baz) instead (see issue #29625) --> src/main.rs:175:10 | 175 | where F: FnMut<(u8,), Output=bool> | ^^^^^^^^^^^^^^^^^^^^^^^^^
VSCodeからWSL上のRLSを使うメモ (2)
ある朝突然、 cargo test
が VSCode Insiders から呼べなくなった。これを踏んでた。どうやら昨日の夕方に 1.36.0-insider がアップデートされて壊れたようだ(アップデートされた記憶はあまりない)。
以下に従って5月のビルド 1.35.0-insider に戻したら問題なく動いた。 Insider らしくてとてもよい(よくない)。
Access older Insider builds · Issue #46089 · microsoft/vscode · GitHub
VSCodeからWSL上のRLSを使うメモ
悲しいことにうまく動せていない。時間が解決してくれる気はする。
その1. Remote WSL環境
開発者版 VSCode insiders が必要。 VSCode とは別のアプリとしてインストールできるので気にせず入れて良い。Remote WSL 拡張を入れればそれで終わり。
その2 で使う useWSL
は使わない。ほぼうまく動くが、なぜか rustfmt
による整形が動かない上に、 Output View へログを一行も吐いてくれない*1ので調査もできなくて困る。
その2. RLS拡張のuseWSLを使う
RLS拡張 が想定しているのはおそらくこちらの使い方。
しかし、まず rustup の PATH が通らない。 issue 上がってそう だけど放置されてる感。拡張の設定で rustup
の PATH を WSL 内の full path にすればこの問題は対処できる。
次に嫌なのが、 WSL 内の windows のファイルシステムのマウントが /mnt/固定であること 。 /etc/wsl.conf
に root
設定を書いていると動かせない。
root
を変えるのが面倒でここで諦めてしまったが、使っている人がいる機能であるはずなので多分動かせば動くのであろうroot
を変えたら動いた。あと、当たり前ではあるがこちらだと、 windows のファイルシステムにプロジェクトを置かなければならないという制約がある。
*1:その2でもきちんと動くようになったあとはログが出なくなったので、エラーが起きないとログは吐かないという話のような気もする