○簡単ではないRust

Rustは、開発者から愛されてやまないプログラミング言語なのだが、残念ながら楽ちんとは言い難い。インタプリタが利用できて、数行入力すればいろいろ試せるタイプのプログラミング言語ではないのだ。こうした点から、Pythonといったプログラミング言語とは違う立ち位置にいるのだ。

最初に学ぶプログラミング言語としては難しすぎるかもしれないが、すでにプログラミング言語の経験があるユーザー、特に複数のプログラミング言語の経験があるユーザーの心はガッチリとつかみそうだ。「そうそう、これが問題なんだよなぁ」という部分が解決されているからである。

その分簡単ではない。シンタックスはC/C++に似ているとは言われるものの、C/C++のすべてのキーワードが実装されているわけではないし、Rust独自のキーワードも導入されている。書き方によっては、本当にC/C++に似ていると言ってよいのか、と頭を捻りたくなることもあると思う。

What is Rust and why is it so popular? - Stack Overflow Blog

Rustのビルド環境を構築する前にもう少しRustの特徴を紹介しておこうと思っていたところ、ちょうどStack Overflowに公開された記事「What is Rust and why is it so popular? - Stack Overflow Blog」がその目的に合っていたので、この記事を参考にRustの特徴を紹介しよう。

なお、以降、意味不明なキーワードが頻出するかもしれない。今、理解する必要はないので、ぼんやり覚えておいてもらえればと思う。

○C/C++ユーザー向け: 借入チェッカー、ゼロコスト抽象化、Unsafe、40年設計

Rustに興味を持つユーザーの多くが、C/C++の経験を持っているのではないだろうか。Rustはなんといってもメモリセーフで「実行速度がC言語と同じ」という点が魅力だ。「もうC/C++のメモリエラーから開放されたい。でも、遅くなるのは絶対許せない、だけど、もうこの言語では無理じゃないか」と感じているなら、Rustはよい選択肢だと思う。

Stack Overflowの記事では、C/C++ユーザーにメリットをもたらすRustの特徴として、以下を挙げている。

借入チェッカー

ゼロコスト抽象化

unsafe

40年を見通した長期設計

C/C++を使ってからRustに触ってみた場合、最初に心強く、そして最高に苛立ちを感じるのは、Rustの「借入チェッカー(borrow checker)」だ。借入チェッカーは、コンパイル時にポインタや参照のライフタイムや使用範囲を判断する機能だ。仕様上の範囲を越えて利用しようとしていたら、コンパイルを許可しない。コンパイルしたいなら直すしかない。

C/C++ではポインタや参照を使いまくるわけだが、時々保証されていない使い方をしてしまうことがある。その使い方は仕様上は定義されておらず、どのような動作をするかわからないのだが、いわゆる「俺のところでは動いている問題」というやつで、ビルドして実行したら動いたのでそのまま使っているということが結構ある。そして、「別のOSでビルドするとコアを吐く」「コンパイラが変わると不思議な挙動が始まる」など、悪夢が始まるのである。

Rustでは、それがない。Rustはコンパイル時にライフタイムと範囲を追っていくので、ダメなものはダメという。最初はこのエラーにかなりイラッとすると思うのだが、考えてみれば、コンパイルの段階でここを直すのが本来あるべき姿であって、チェックしてくれるほうがよいのだ。最近のC/C++コンパイラは、似たような状況を警告してくれるようにもなってきているのだが、完全ではない。

ゼロコスト抽象化もよく言われるRustの特徴だ。ある程度プログラミングスキルが上がってくると、コードの抽象化にハマる時期がある。なんでもかんでも抽象化すればよいというものではないが、コードの再利用が進むと言われており、比較的行き着く先なのである。

しかし、この抽象化はコストを伴うことが多い。要は「遅くなる」のである。Rustの抽象化は遅くならない。それを示す言葉がゼロコスト抽象化だ。先程の記事では、その例として次のコードを掲載している。遅くならないことは、Rustに求められる至上の命題ということになる。

資料: Stack Overflow提供

let squares: Vec = (0..10).map(|i| i * i).collect();

Rustは結構厳密な言語なのが、そうなるとRustでは表現できない部分もでてくる。Rustはその部分をunsafeとして扱えるようになっている。unsafeな部分は可能な限り小さなほうが良いと考えられている。unsafeの中の動作はプログラマが動作を保証するというか、問題がないように組む必要がある。unsafeはRustが現実的な現場で使われるプログラミング言語であることをよく示している。

C/C++ユーザー向けに最後に紹介されている特徴が、40年先を見据えた設定になっているという主張だ。本当かどうかはわからないが、Rustは下位互換性と安定性に関する設計上の意識的な決定を下しており、向こう40年間使用できるシステムプログラミング言語だと考えられている。

○ガベージコレクションあり言語: システム一部置き換え、メモリ効率化、組み込み/IoT

Rustはガベージコレクタの動作を必要としないため、ガベージコレクタがもたらす問題が発生しない(ガベージコレクタの動作するタイミングで動きがカクカクするとか、ガベージコレクタが動きまくってリソースが食われるとか)。加えて、ガベージコレクタを必要としないので、他のプログラミング言語が使用するライブラリを実装するのにも向いている。既存のシステムやアプリケーションの一部をRustに置き換えるといったことが容易にできるということだ。

また、Rustはコンパイル時に利用するメモリをスタックにするか、ヒープにするかを選択できるという特徴もある。これによって、メモリの効率的な利用と高いパフォーマンスを実現できるようになる。

Rustはシステムプログラミング言語だ。当然、システムの低い階層を詳細に制御できる。ハードウェアおよびメモリに直接アクセスでき、組み込みやベアメタル向けのプログラミング言語としても使用できる。

○動的型付け言語: NULL安全、明確な型と型推論

Rustは静的に型付けされた言語に分類されるが、他の静的型付け言語と異なり、NULL安全という特徴がある。NULL安全は比較的新しいプログラミング言語に導入されている概念だ。

NULLは便利な一方、障害の原因にもなる。例えば、実行時に「TypeError: Cannot read property 'foo' of null」といったイラッとするエラーの原因になる。RustはNULLに相当する状態の処理を記述することをコンパイル時点でチェックして要求する。書いてないとエラーになるのである。

記事には、サンプルとして次のソースコードが掲載されている。Optionという表記と、matchの中のNoneが該当部分だ。この値はやってこない可能性があることをOptionで指定し、その場合の処理をmatch Noneに書くというものだ。コンパイラで強制されるので、処理を書かずに問題が発生するといったことが起こらない。

資料: Stack Overflow提供

fn greet_user(name: Option) {

match name {

Some(name) => println!("Hello there, {}!", name),

None => println!("Well howdy, stranger!"),

}

}

静的型付けは厳密に適用されると、面倒なことになりがちだ。かといって、全面的に型推論を導入すると、結局静的型付けのメリットが消えてしまう。Rustはこの部分のバランスが絶妙で、関数定義や定数などでは明確な型指定を要求するが、関数内部では型推論を許可している。記事では、次のサンプルコードを掲載している。

資料: Stack Overflow提供

fn simple_math(val: i32) -> i32 {

let twice = val * 2;

twice - 1

}

Rustは後発の言語であることも関係しているのだろうが、この辺りのバランスが現実的なところに落とし込まれている。

○理解しやすいエラーメッセージ

記事では、コンパイラの表示するエラーが理解しやすいことも挙げられている。次のスクリーンショットは、記事に掲載されているソースコードを編集してビルドしたものだが、変更不可能な変数を変更可能な参照として利用しようとしているのでビルドがエラーになっており、その理由もエラーメッセージにちゃんと書いてある。

わかりやすいエラーメッセージ

そのほか、Rustを取り巻くエコシステムが健全に機能していることも紹介されしている。ビルドツール、開発者コミュニティ、ユーザーコミュニティなどがうまく回っており、情報共有サイトなどの活用も進んでいる。

逆に頼りない点としては、ライブラリの種類がまだ豊富ではないことが指摘されている。ライブラリは実際に開発して増やす必要があるので、今後成長していくことになると思う。

Rustにはさまざまなプログラミング言語のパラダイムが導入されているので、まだまだ特徴的な機能がある。次回からは、サンプルコードを作成しながらRustを試していこう。