Rust 入門もどき(というより体験記)
Dec 16, 2015 22:09 · 4529 words · 10 minute read
※この記事は coins Advent Calendar 2015 と AmusementCreators Advent Calendar 2015 の19日目(多分)の記事1です。
大学の後輩たちがやっている Advent Calender に何か書きたいなーと思っていたら、 何となくネタがあるような気がしてきたので書いてみます。 とはいえ僕自身そんなにRustに書き慣れていないし、ドキュメントもそんなに読んでいないので、間違いがあったら優しく教えてください。
Rust という言語は様々な言語の影響を受けているそうですが、個人的にはRustの設計思想は C++ と OCaml などの ML系言語 の影響がかなり強いという印象を持っています。従って、これら2つの言語の知識があればこの言語をより理解しやすいと思います。ただ、本記事はスタック領域やヒープ領域などの基礎単語さえ知っていれば、他の言語(coins の授業で使われる Java など)の知識のみでも読めるように書こうとは思っています。
環境構築と Hello, World!
- インストールするのが面倒なら、 Wandbox でお試しができます。 Wandbox を使う方はこれで環境構築終了です。
インストールするのが面倒でないなら、ダウンロードページから Rust のバイナリを落としてインストールしてください。Stable の他に Beta と Nightly というチャンネルがありますが、少なくない数のライブラリが Beta や Nightly にしかない機能を使用しているので、最初に Nightly で書いてみて、動かないライブラリが出たら Beta や Stable に戻す、というやり方をおすすめします2。このときついでに rustc のソースコード3も落としておいてください。次にインストールする Racer が使います。
シェル上で
cargo
コマンドを叩いてみて、正しくインストールされたことを確認したら、Racer をインストールします。READMEを読んでその通りにやればすんなり入ります。こいつがあるとエディタ上で補完が効くようになってめっちゃ便利です。適当なディレクトリで
cargo new --bin helloworld
を走らせます。helloworld/src/main.rs がこれからいじるコードになるわけですが、中身を見ると何故か Hello, World! が書かれているので、 helloworld ディレクトリで
cargo run
を叩いて Hello, World! 終了です。
言語仕様メモ
チュートリアルは本家のサイトに割合しっかりしたのがありますし、ライブラリリファレンスも充実していますので、ここでは言語の全般的な紹介は行わず、自分が実際につまづいた点、他人がつまづきそうな点を中心に、いくつかの Tips のようなものを書いておきたいと思います。
ownership と borrowing, lifetime
Rust には言語仕様としてガベージコレクション( GC )がないため、メモリ管理をプログラマの責任で行わなければなりません。とはいえ C 言語のような原始的なやり方ではなく、 コンパイル時に潜在的なバグの原因を検出する、下に述べるような機構や概念を持っています。
ownership
ownership(所有権) は、変数が束縛(≒代入)されたリソース4を消去する権利のことです。
具体的には、あるリソースの所有権を持っている変数は、スコープ後端に達して寿命が尽きた際にそのリソースを解放します。 ただし、所有権は一つのリソースに対しちょうど一つの変数しか持てない(リソースの解放操作を複数回やると困るので)ため、別の変数に代入する場合には所有権の移管が発生し、元々の変数はそのリソースを利用できなくなります5。ただし、所有権を持っていない変数も所有権を借riりる(borrowing; 後述)ことでリソースを利用できるため、別の変数に代入できなくてもそれほど問題にはなりません。
要するにどの変数がそのリソースの後始末を任されているか、という考え方です。また、数値などのヒープ領域に格納されない小さなデータでは、代入時に所有権の移管ではなくコピーが発生します6。
borrowing
あるリソースの所有権を持たない変数は、参照7によってそのリソースにアクセスすることができます。
参照によってリソースにアクセスする場合、型が先頭に &
がつく参照型に変わりますが、参照型と元の型を相互に変換するために &
や *
といった演算子を使う必要が生じるだけで、実際他はそんなに変わりません。
ただし、マルチスレッドでのデータ競合を避けるために、あるリソースへの 書き換え可能な参照( &mut
)は同時に一つしか存在できない という制約があります。
この辺が C++ での所有権や参照と大きく異なる点です。
lifetime
Rust では、前述の所有権によって、リソースの寿命をコンパイル時に決定できるようになっています8。 そこで、その寿命の長さ(=所有権を持つ変数のスコープ)に名前をつけて陽に扱おうという考えが出てきます9。これが lifetime というものですが、入門したてだと使う機会もないので、必要になったら使い方を覚える、という感じでいいんじゃないかと思います。
Box, Rc, Cow, Cell
ここでは比較的よく使われ、また一見使い分けが難しいいくつかのコレクションやスマートポインタについて、自分なりの理解を書いておきます。
Box
Rust は C++ や C などと異なり、ヒープ領域にデータを置くための操作( Boxing )を言語機能ではなく、Box
という名前で標準ライブラリの一部として提供しています。
要するに C++ での new
, C での malloc
に当たるのが Box::new
です。動的配列などのコンパイル時にサイズが分からないデータ構造や、二分木などの再帰したデータ構造、巨大な配列などに用いられます。
他との使い分けとしては、所有権や参照の振る舞いは Rust の原則通りでいい、単純にヒープに置きたいリソースのために使うのがよいようです。
ぶっちゃけあまりによく使うので、いちいち Box::new
と書くのが面倒になってきます10。
Rc
Boxのようにデータをヒープ上に置くために使われますが、Box
が C++ での new
ならば、こちらは shared_ptr
にあたり、参照カウントで所有権の管理を楽にします。Box
との使い分けとしては、とにかく所有権の管理が面倒だったり、所有権をたらい回しにする必要があったり、寿命が不明確なリソースに用いるとよいようです。あるいは C++ の shared_ptr
のように、どちらを使うか迷ったらとりあえず Rc
を使えとかいわれる時代が来るのでしょうか……。ただし循環参照の検出はしていないので、リソース同士がお互いを参照し合っている場合などでは、弱参照を適切に使うなり注意が必要です。
循環参照を避けられない場合、標準ライブラリではありませんが、ほぼ同じように使えるマーク&スウィープ方式の Gc
というライブラリもあるようです11。また、標準ライブラリにはマルチスレッド向けに、アトミックに参照カウントを行う、 Arc
というスマートポインタも用意されています。
Cow
Cow
は書き換えや所有権の取得が必要になるまでデータのコピーを遅延させる、コピーオンライト12 のためのスマートポインタです。
データを書き換える可能性があるのでコピーがほしいが、書き換えずに済む可能性もある場合に、コピー時の無駄なコストを抑えるために使います。
Cell
Cell
は set
と get
というメソッドを持っており、&mut
ではなく通常の &
から内部のデータを書き換えることができます。
つまり、通常は一つのリソースにつき同時に参照一つまでしか書き換えができない(&mut
は同時に一つしか存在できないので)のに対し、Cell
ならば複数の参照から書き換えができるようになります。寿命が明確な一つのリソースを、複数の変数から書き換えたい場合に用いるのがよいようです。
なお、Cell
はスタック上に置かれる型専用であり、ヒープ上に置かれるデータの型は RefCell
を使う必要があるようです。
外部ライブラリ
Rust は外部ライブラリを Cargo というサイトで集約的に管理しており、プロジェクトの設定ファイルに一行書くだけでライブラリを簡単に導入できます。 この章ではそのサイトに登録されているライブラリの中から、使って便利だったものや面白そうなものを紹介したいと思います。
Piston
せっかくゲーム製作サークル AmusementCreators の Advent Calendar なので、ゲーム製作に関することを書いておきたいと思います。
Piston は、 Rust で書かれた OpenGL ベースのマルチプラットフォームゲームエンジンです。 現在も開発が盛んですが、 3D やサウンド、テキスト、 GUI 用のライブラリも付属するなど完成度自体は高く、十分実用に耐えうるレベルのようです。
また、ここの開発コミュニティはとても野心的で、ゲームエンジン本体のみならず、VisualStudio プラグインや強力な DSL パーサライブラリなんかも作っているので、 その辺ものぞいてみると面白いかもしれません。
regex
regex は、 名前のごとく正規表現ライブラリです。 何故か標準入りしていない13正規表現ライブラリですが、このライブラリは機能も申し分ないですし、Rust のコアメンバーが開発しているので、早く標準に入ってほしいとは思います。
rust-peg
rust-peg は Parsing Expression Grammar (PEG)という、新しくて分かりやすい形式文法を用いてパーサを記述できるライブラリです。 PEG で記述できるパーサライブラリはいくつかあるのですが、いろいろ試した結果これが一番使いやすいような気がします。 個人的に Rust は新しい言語を書くのにうってつけの言語だと思うので、 Rust を試したいけど何を書こうか迷ってるという方は、とりあえず言語を実装してみると Rust の魅力が理解できるのではないでしょうか。
構文解析によく使われる yacc のポーティングとしては、 RACC が割合いい感じだとは聞くのですが、ドキュメントがない上に公式に未完成だと書いてあるので、試していません……。
jvm-assembler
どうせ言語を組むなら、インタプリタもいいですが既存のアーキテクチャに向けてコンパイルしたいですよね。 とりあえずアンケートを採ったら JVM に吐きたくなったので、いい感じのを探したら jvm-assembler というのがあったので使ってみようと思います。 Jasmin を使うより手軽そうですし。吐いたブツが正しく動くかはまだ試していませんが……。
最後に
最初は型システムやマクロ、 derive attribute あたりの面白いところも書こうと思っていたんですが、こればっかり書いていると卒論に全く手がつかないし、 なによりこれ以上デタラメを書いてると怖い人からマサカリが飛んできそうなので、この辺で筆を置きたいと思います。
いろいろ書いてきましたが、パターンマッチと代数的データ型と型クラスがあって手続き型が楽に書けて、C++ 似のメモリモデルを持っているというだけでとても面白い言語だと思うので、皆さんも書いてみてください。
- ふたつの Advent Calender に同時に投稿するのもアリといわれたので、ちょっとせこいようですが…… [return]
- あるいは multirust を導入するのも手かもしれません [return]
- ダウンロードページ右側の Source ボタン [return]
- ここでは、
Vec
などのヒープ領域に置かれたデータ [return] - borrowing があるから、所有権を渡した後でも参照で見るくらいいいじゃないかと思いがちですが、所有と参照だと型が違うので…… [return]
- 厳密には
Copy
trait によって振る舞いが決定されます。この辺は C++ や Java よりも融通が利いていていいと思います [return] - あるリソースの置かれている 場所 を記憶することで、そのリソースにアクセスする方法 [return]
- 参照カウントなどで動的にすることもできます [return]
- region という考え方で、ML Kit などの言語で寿命をなるべく静的に決定し、実行速度を上げるために使われているそうです [return]
new
並みに簡単に書ける、box
キーワードが検討されてはいるみたいですが…… [return]- ただし未完成な上に更新頻度が低いようですが…… [return]
- ただし
Cow
自体は Clone-On-Write の略らしいです [return] - 少し前までは GraphViz 用のライブラリすらはいっていたのに…… [return]