年末年始 Rust 振り返り

年末年始の短い冬休みは何もすることがなく(いや、することはいくらでもあるんですが)、ずっと Rust のコンパイルエラーと見つめ合っていました。去年は後半から久々にそこそこの時間を Rust に費やしたので、思ったことを振り返りたいと思います。

所有権と生存期間は理解できてからが本番

Rust と言えば所有権と生存期間です。難しいと言われる理由です。難しいです。難しいのでドキュメントや Rust 本でもページを割いて解説されています。 Rust 本も出版されましたし、理解しやすくなったと思います。

が、所有権と生存期間は理解できてからが本番です。ある程度の規模のコードを書いて間違えて書き直してを繰り返し、設計と実装のトレーニングを積まないと身につきません。コンパイルエラーが出るたび、まだまだ理解が浅いと痛感します。 GC がないとはそういうことです。

とにかくデータ型の設計に苦労する

Rust ではデータ型の設計にとにかく気を使わざるを得ません。適当にやったらコンパイラに止められます。設計の際は次の要素を考慮する必要があります。

  • 循環参照の有無 (Rust では一方通行のデータ型が望ましい)
  • 可変性 (mut で済むのか済まないのか)
  • 所有権 (コピーするのか参照を使うのか)
  • 生存期間 (<’a> <’a> <’a> <’a> 以下略)
  • スレッドセーフ

神経をすり減らしながらコンパイルエラーと戦って、やっとすべてのエラーを解決したと思ったら新しいエラーがずらずら出てきた辛み。私は実装中のコードを並行処理にしようとしたら、ほぼすべてのデータ型を根本的に見直す羽目になりました。 GC がないとはそういうことです。

関数の引数は思考停止で参照渡しとけというわけにもいかなくて、やっぱり生存期間の理解が甘いとどこかで引っ掛かかります。私は Erlang VM を実装してみているんですが、 Erlang オブジェクトは可変なデータもあり、オブジェクト間で共有されるデータもあり、どうしても Rust way から外れる方法で管理しないといけません。結果、生存期間を Rust コンパイラが把握できなくなったので、参照の扱いでさんざんエラーが出たりして辛いです。実装の都合で所有権を Rc/Arc (参照カウント) で管理することが多いんですが、そうすると参照を受け取っても Rc/Arc でラップし直さなければならない場合も多いし、 Rc/Arc だと渡すたびにカウントの増減が発生するし、 Arc だとアトミック操作のコストもあるし、そうなるとパフォーマンス的にどうなの?とか考え始めて堂々巡りです。 Rust と VM は英語と日本語くらい相性が最悪なのではないかと思わざるを得ない。

すごく細かい API

Rust はリソースが制限された環境 or リソースを管理したい環境向けの言語なので当然と言えば当然なんですが、標準の API が細かいです。文字列一つとっても複数の型があり、文字コードの扱いもあって相互変換に手間がかかります。他の API も、動的なメモリ確保を避けるためにあれやこれやと回り道をしており、理解しないと先に進めません。 GC がないとはそういうことです。

トレイトも大量にあります。 Rust では基本的な機能のかなりの部分がトレイトで表現されていてリソースを制御可能なんですが、金輪際あなたに必要がないとしてもいつか理解しないと先に進めません。 GC がないとはそういうことです。

もっとも私は標準 API にあまり文句はありません (不満がないとは言ってない) 。この細かさが必要な言語でしょうから。

unsafe のジレンマ

unsafe を使ってポインタを直接操作すれば 1 行で済んでかつ高速になるのにと思う処理にときどき出くわします。大抵、そういう処理は所有権、生存期間、共有、内部可変性、非スレッドセーフなどの問題でがんじがらめです。 Rust way に従うと一気にコードが膨らみます。もちろんそうならないような設計をするのがベストなんですが、アプリケーションの性質によってはそうもいかないでしょう。 GC がないとはそういうことです。

しかし、 unsafe を使うと当然ながらメモリ安全ではなくなってしまいます。メモリ安全ではない箇所が一つでもあれば、アプリケーションすべてがメモリ非安全です。 Rust の面目丸潰れです。「 Rust は自分の足を撃てない」と言われますが、 unsafe と一言書けば今すぐ撃てます。ですので、安全なアプリケーションを作りたいなら頭を捻って unsafe を全力で回避すべきです。大変ですよマジで。 GC がないとはそういうことです。

ちなみに VM はそういう処理の塊です。 Rust で実装中の Erlang VM は他にもいくつかありますが、わりと気軽に unsafe が使われています。本家 VM の理解や実装自体が目的のようなので問題はないと思いますが、もし Rust の学習目的で何かしら作るなら unsafe を使ってしまうとレベル上げの妨げになる可能性がありそうです。 GC がないとはそういうことです。

意外と使い道がない?

Rust って、使い道が多いようであまり使い道がないんじゃないかと思うんですよ。

メモリ安全であるのは確かに大きなメリットですが、制限されたリソース内でやりくりする必要性が薄い仕事をしている大多数の人(つまり GC つきの言語で普段困ってない人)には、学習を含めた開発コストを考えるとリターンが割に合わない場合が多いんじゃないかと思うんですよ。 GC の停止時間がなく、メモリ安全で、かつ高速という特徴が魅力的に映るのはわかります。でもそれ、 99% の案件は Go で解決できそうなんですが無理ですかね?

もちろん、札束で頬をひっぱたいて人を連れてこれる企業なら別に自分が習熟しなくたっていいので問題はありません。ですが、あくまで習熟に関して、です。あなたが雇った人が度重なるコンパイルエラー及び unsafe を回避するための設計に疲れ、濁った目で unsafe と書いてるかもしれません。いくら Option や Result でエラーを可視化しても、エラー処理が面倒で foo().unwrap().bar().unwrap().baz().unwrap()… と書いてるかもしれません。となると、本当に安全な Rust アプリケーションを作りたいならコードレビュー(片手間で済まないレベルの)が必須になるはずです。 Rust はメモリ安全を実現してくれるツールではなく、あなたがメモリ安全なコードを書いたかどうかを検証してくれるツールです。 何度書き直してもエラーを出すコンパイラに文句を言っても仕方ありません。何度も非安全なコードを提出するあなたにコンパイラが文句を言ってるんです。

所有権と生存期間の概念に学びがありそうだと思うのもわかります。確かにあるとは思います。でもなー、こう言っちゃなんだけど所有権と生存期間って(大多数の人には)応用範囲がかなり狭いのではないかと思います。普段 GC つきの言語を使っていて、メモリについて意識したことないから勉強したいというなら、お使いの言語の GC のアルゴリズムについて学んだほうが役立つだろうし知識の底上げにもなるんじゃないかと思うんですよ。

だって、時間は有限だと思うんですよ。仕事で Rust を使うことになったとか、次の仕事の有力な候補だとか、学習の必要性がある人以外は、その時間を他の言語や技術の学習に使うほうが有意義だと思うんですよ。所有権と生存期間の考え方を身につけるなら相当な時間の投資が必要です。だったらその時間を英語か Go の学習にでも当てたほうがいいです。真面目に。強い型付けと高階関数が学びたいなら OCaml もいいですよ。 Real World OCaml に GC の解説もあります。 Rust がわからん OCaml マスターがいても、 OCaml がわからん Rust マスターはいません。たぶん。

なんでこう悲観的否定的なことばかり書くのかと言うと (私の書く記事はそんなんばっかりですね…) 、関数型言語は難しいと敬遠するのに同じ特徴を持つ Rust には好印象を持ったり、開発経験なしに Rust に未来を確信する人が多く見受けられる感じがするからです。いえ別にどう思おうと自由ですよ? ただ、個人的に見たり聞いたりするうちに、ちょっと一言言いたくなったのです。