Rust 1.0がリリースされたのは2015年の5月で、5年前のこととなります。自分は1.0 betaのころにRustを始めたので、なかなか感慨深いものです。この5年の間に、いくつもの企業でRustを採用、もしくはその検討がされるようになり、ゆるやかであるもののRustの普及が進んでいると感じます。
そこで、1.0から5年経ち、だんだんと普及しつつあるこの時期に、なぜRustを使うべき、学ぶべきなのか、改めてまとめてみようかと思います。Rustを簡単に紹介する場合、メモリ安全とパフォーマンスの両立、高い生産性をもたらす言語機能などが挙げられることが多いですが、自分なりにRustを学ぶ意味を掘り下げてみようと思います。
なお、以下の文章はほとんど個人的な経験に基づいて書かれたものですので、その点はご注意下さい。
Rustはほどよい機能を提供する言語である
世の中には様々なプログラミング言語があり、それらが提供する言語機能もまちまちです。どういった言語機能を提供するかは、その言語の設計思想に基づくものですが、万人に受け入れられる必要十分な言語機能を提供することは難しいことです。
膨大な言語機能が提供されていると、不適切な使われ方をされた場合、かえってコードが複雑で保守しにくいものになりえます。かといって、ある問題を解決するために適した言語機能が存在しない場合、冗長なコードを書くことになりがちです。言語仕様がシンプルだからといって、解決策もシンプルになるとは限りません。
Rustの場合、言語機能の取捨選択はかなりうまくいっていると感じます。クラスや継承といったものはありませんし、関数のオーバーロード、例外によるエラー処理といった機能もありません。このような機能がある言語を書いてきた人がRustに触れた場合、最初は戸惑うかもしれません。しかしながら、実際使ってみると、先程あげたような機能が欲しくなる場面は実際には1割も無く、もっと適切な手段が用意されていると気付きます。
また、Rustは敷居の高い言語として知られており、それゆえ言語も複雑なのかと思いきや、実際には、覚えておかなければならないものは2つのシンプルなルールしかありません。すなわち、所有権と、トレイトを中心とした型システムです。Rustの言語機能は基本的にこの2つのルールを中心としており、ここに馴染めればどの言語機能も理解するのは容易いはずです。これをもって「Rustは実はシンプルな言語」と言い切ってしまうのは少々乱暴ですが、Rustの言語機能を活用することでかえって複雑で保守しにくいコードになってしまった、ということは起こりにくい、ということは言えます。なぜならこの2つのルールと、それに基づく言語機能は、書かれたコードを後から読んでもわかるように適切に意味付けする助けとなるからです。
なお、この2つのルールから外れた言語機能がRustには存在します。それがマクロであり、やはりRustのマクロも乱用すると保守性を低下させる危険性を持っています。しかしながら、宣言的マクロ(macro_rules!
で定義するもの)は単純な使い方でもコード内にある冗長性を削減する強力なツールになります。また手続きマクロは、コンパイラができることはほとんどなんでもできてしまうほどの自由の代わり、実装する難易度が高い代物です。しかしながら、すでに用意され、広く使われている手続きマクロを利用することはなんらためらうものではありません。例えばserdeは、これだけでRustを採用する理由になるほど強力なフレームワークです。
Rustの言語機能は基本的に、所有権とトレイトを中心として手堅くまとまっており、たいていの場合は事足ります。しかしながら、マクロという機能を提供することで、不満があれば拡張できるという形をとっています。なかなかバランスのとれた言語設計と言えるのではないでしょうか。
Rustはあなたの記憶領域の節約を助ける
プログラミングという作業は、大量に脳の記憶領域を消費します。まず、言語の知識、ライブラリやフレームワークの知識といった、たいていは長期記憶に格納されるであろう情報があります。それに加え、今書いている目の前のコード、例えばある関数の返り値の型、変数の定義場所等、短期記憶に格納される情報があります。
もし、コード中のある部分が適切に他のモジュールと分離されていないと、書き直すのに必要な知識が多くなります。なぜなら、書き直したら別の部分に影響が及ぶかそうでないかを把握しなければならない範囲が大きくなるからです。一方、人間の短期記憶の容量と期限はとても小さいです。変数の有効範囲なんて、コードの該当部分を読んだ直後に忘れてしまうかもしれません。そうなると、人間の短期記憶には収まらず、書き直しは困難なものとなるでしょう。
このような問題を避けるため、プログラミング言語には何らかのモジュール化の機能が用意されています。もし人間の記憶容量と読解力が十分に高ければ、現在のプログラミング言語はifとgotoとグローバル変数だけで書かれているはずで、そうなっていないのは、人間が書きやすいように問題を分割する手段を提供する方向に言語が進化してきたためでしょう。
短期記憶の節約という意味において、Rustは従来の手続き型言語より進化しています。型や所有権といった言語機能により、目の前のコードのうち見なければならない範囲を小さくしてくれています。
例えば、RustにはNULLがありません。これにより、目の前にある参照が無効にならないことが明確です。参照を作っている元をたどったり、ドキュメントを探したりする必要がなくなります。参照が無効になる場合はOption
で囲ってやれば良いのです。その場合、参照が無効である場合に対処しなければならない、という情報が得られます。そしてこれらの情報をプログラマが覚えておく必要がありません。必要なのはOptionと参照についての言語自体の知識となります。
また、所有権やライフタイムも強力な機能です。もし目の前の参照が可変参照&mut
であれば、同時に同じところを指す参照が無いことが保証されます。意図しない変更をしないかどうか、コードの他の部分を見る必要はありません。また逆に、不変参照&
は、他の部分から書き換えられたりしないことが分かります。参照先のオブジェクトが書き換えられるのはいつか、それは自分で参照を保持している間であれば必要のない知識です。
実際にRustを使ってみると、RustはGCを無くすための代替策として仕方なくこんなめんどくさい所有権という概念を持ち込んだ、というわけでは決して無いことが分かります。一見面倒に見えるルールは、プログラマが楽をするために存在します。ルールに従って書かれたRustのコードは、それ自体が情報を保持し、プログラマの行った修正が正当かどうかは、その情報を用いてコンパイラが判定してくれます。Rustコンパイラはあなたの敵ではありません。実際には、プログラマがコードの細部まで記憶して置かなくても済むように、あたかも外部記憶装置のように動作します。
もちろん、Rustでは覚えて置かなければならないルールが多いのは確かです。しかし、それは一度長期記憶に入れてしまえばずっと役に立つものです。新しいプロジェクトに関わる度に、不完全かもしれないドキュメントや、コードの細部まで読んで記憶するよりは、ずっと脳の記憶領域を効率的に活用できるでしょう。
Rustで学んだ概念は陳腐化しない
はじめはRustで何かを書こうとするたびに、壁にぶつかることが多いでしょう。ちょっとしたリソース管理をするだけで所有権について習得しなければならないし、別スレッドで処理をさせようとするとSend
やSync
等々色々知らなければならないことが出てきます。もっと親切な言語であれば、こんなこと知らなくてもさっと書けるのに!
もちろん、Rustは理由もなくこういった概念を導入したわけではありません。これらは、ハードウェアに起因する制限と、それに対する解決策をRust流に翻訳したものにすぎません。所有権は、コンピュータ上のリソース管理を明文化したものです。そして、Send
やSync
は並行処理における制限や条件を明文化したものです。もしその制限に違反するコードを書いた場合、Rustのコンパイラはエラーを吐き出します。他の言語であれば、違反によって問題が起こるまで何もエラーは出ないか、もしくは問題が起こっても気づかないだけです。
現代の高級言語であれば、GCやVMによってこのようなメンドクサイ部分は巧妙に隠されています。しかし、隠しきれずに顕在化する場合もあります。遅かれ早かれ、リソース管理や並行処理の難しさを味わうことになるでしょう。
その点、Rustは初心者にも容赦なく、新しい概念を学ぶことを強要してきます。しかし、それらは結局、CPUとメモリから構成される現代の計算機に起因するもので、必ずしもRustでしか役に立たない概念ではないと気付きます。そして、Rustのコンパイラは、エラーメッセージを通して、その問題に対処する方法を教えてくれます。
つまり、Rustを学ぶことは、計算機でのプログラミングそのものの難しさと解決策を学ぶことに繋がります。仮にRustより使いやすく、Rustを置き換えた他の言語が登場したとしても、現在のコンピュータの構造が大幅に変わらない限り、Rustで学んだことは陳腐化することはないでしょう。
RustはCと協力できるし、代わりにもなれる
Cは実際古い言語ですが、いまだ現役の言語です。Cが重要である理由の1つは、そのABIの単純さによって、あらゆるプラットフォームの共通インターフェイスとして機能しているところです。多くの実用的な言語は、何らかの方法でCの関数を呼び出す機能を備えています。Rustも当然Cの関数やオブジェクトを言語のコア機能で扱うことができ、これまでCによって開発されてきたライブラリ等を利用することができます。
Rustの利点は、これまでのCの資産を利用できることに限りません。Rustの関数をCから呼び出せるように書くこともでき、それはすなわちRustでかかれたライブラリを他の様々な言語から呼び出せることを意味します。Cを直接書かなくても、Rustを利用すればCと同等の可搬性のあるライブラリを記述することはできます。その場合、Cで得られなかった高級な言語機能を利用することができます。
CのABIを介して他の言語から使われることを前提としたRustのプロジェクトはまだそう多くありませんが、CloudflareのQUIC実装であるquicheや、GNOMEのlibrsvgなどは、中身をRustで書きつつCのインターフェイスを出すようにしているようです。
また、Rustには継承といった機能がなく、それゆえCでの伝統的なカプセル化した書き方にそのまま変換しやすく、Rustでなんとなく書いてもCのインターフェイスを出しやすいという利点もあると思われます。
もちろん他言語から利用されるライブラリだけでなく、組み込み等の、これまでCが用いられてきた領域でもRustを採用する余地はあるでしょう。とくに近年では、Cが用いられてきた領域でもセキュリティ面の向上が求められています。Rustにしたからといって脆弱性が無くなることが保証されるわけではありませんが、Cのメモリ管理に起因する脆弱性が削減されるだけで、Rustを採用する価値はあるでしょう。
最後に
Rustを学ぶことは、今後必ず役に立つ知識や経験をもたらしてくれます。もちろん、Rustは十分実用的な言語であり、現実のプロジェクトでその利点を発揮させることもできます。Rustは敷居が高いと言われてはいますが、この5年の間、Rustは着実な進歩を遂げています。Rustを始めてから幸せになれるまでの労力も、5年前と比べてずっと少なくなっているはずです。みなさんもこの機にRustを学んではいかがでしょうか。