Rust (プログラミング言語)
Rust(ラスト)は、性能、メモリ安全性、安全な並行性を目指して設計されたマルチパラダイムのプログラミング言語である。C言語、C++に代わるシステムプログラミング言語を目指しており、構文的にはC++に似ているが、「ボローチェッカー」(borrow checker) で参照の有効性を検証することによってメモリ安全性を保証できる。Rustはガベージコレクションなしでのメモリ安全性を達成しており、必要な場面で参照カウントを使うこともできる
。
Rustプロジェクトはオープンソースのコミュニティベース開発で進行しており、言語仕様(検討段階含む)、ソースコード、ドキュメントはオープンソースライセンスで公開されている。2006年の開発初期は、Mozillaの従業員のグレイドン・ホアレ(Graydon Hoare)の個人プロジェクトだったが、2009年にMozillaが開発に関わり始めてMozilla Researchの公式プロジェクトとなった。2015年に1.0版がリリースされるまでにいくつもの破壊的な仕様変更があったが、1.0版以降は基本的には後方互換を保って6週間間隔で定期的にリリースされている。
Rustはマルチパラダイムプログラミング言語であり、手続き型プログラミング、オブジェクト指向プログラミング、関数型プログラミングなどの実装手法をサポートしている。基本的な制御構文はC言語に似ているが、その多くが式(expression)であるという点においてはML言語に似ている。コンパイル基盤にLLVMを用いており、実行時速度性能はC言語と同等程度である。強力な型システムとリソース管理の仕組みにより、メモリ安全性が保証されている。
Rustは2016–2022年の間Stack Overflow Developer Surveyで「最も愛されているプログラミング言語」で一位を獲得し続けている。一方で、Rustは学習難易度が高い言語とも考えられており、2017年ロードマップでは学習曲線の改善を目的として挙げていた。
Rustという名前はさび菌に因んで付けられた。この命名の後、当時の開発チームメンバーの多くが自転車を愛好していたことから、自転車のチェーンリングの錆つきを元にしたデザインのロゴが考案された。当然、チェーンリングの錆がさび菌によって生じるわけがなく、「設計者のグレイドン・ホアレが生物学オタクであることから酸化鉄の「錆」ではなく「さび菌」なのだ」と、参考文献の末尾に記されている。
Rustはオープンソースのコミュニティベースで開発が進められている。プロジェクトの主管はRust Project Developers(Rust開発チーム)である。2018年現在、言語発明者のグレイドン・ホアレはプロジェクトチームから外れている。プロジェクトの全てのソースコードはGitHubで一般に公開されており、コミュニティメンバーの協力により改良が進められる。プロジェクトの大部分のコミットはコミュニティメンバーによるものである。
Mozillaの理念を守り、Rust言語は社会に開かれており、言語やツールに対する仕様の提案はRFCの形で取りまとめられ、関連するチケットで仕様詳細について利用者から感想や提言を受けている。
言語やコアライブラリを開発するRust開発チームはServo(ウェブブラウザ用レイアウトエンジン)やRustコンパイラの実装経験を通して言語を改良している。例えば、C言語とRustを連携させるコードを自動生成するbindgenは元々は外部開発者が開発していたが、Mozilla Servo開発チームがServoの開発で利用するためにC++とRustを連携させるコードも自動生成できるよう改良を重ね、現在はServo開発チームと密に連携しているRust開発チームに主管を移行してServoの開発と共に改良が続けられている。
言語実装手法においてのプログラミングパラダイムは特定の実装手法に限定されない非純粋なマルチパラダイムプログラミング言語である。文法の表面的な記述は手続き型プログラミング、ブロックコードのまとまりの意味論は関数型プログラミング、型システムを用いたオブジェクト構造はオブジェクト指向プログラミングのパラダイム特性を持つ。特定のプログラミングパラダイムのみを使用してソースコードを記述することはなく、要所々々の必要な所で必要なパラダイムを使用してソースコードを記述することになる。
ソースコードをターゲットプラットフォームに最適化したオブジェクトファイルにコンパイルするコンパイル言語のパラダイム特性を持つ。多くのスクリプト言語が持つインタプリタ機能や、SwiftやGoが提供するようなREPL機能のパラダイム特性は持たない。
その他のパラダイムとして、関数の入出力パラメータにジェネリック型を指定するジェネリックプログラミング、非同期機能を実現する並行計算のパラダイム特性を持つ。
Rustの基礎的な文法はC言語やC++に似て、波括弧で囲まれたブロックコード{ ... }、if, else, whileなどの制御フローキーワードを持つ。
全てのC言語やC++のキーワードが実装されているわけではなく、一方で幾つかのRustの制御命令(パターンマッチのためのmatch)はそれらの言語を習得したプログラマにとっては馴染みが少ないものも存在する。Rustの命令文は、表面的なC言語やC++との文法の類似性にもかかわらず、意味論的にはML系言語の命令式に近く、関数本体のほぼ全ての部分は制御フロー演算子でさえ「文(statement)」ではなく「式(expression)」である。例えば普通のif式もC言語で言う所の条件演算子であり、if式の結果として返り値を返す。
ブロックコード内の命令文のセパレータにはセミコロン(;)を用いるが、C言語のそれと異なりRustのセミコロンは直前の命令文がブロックコードで括られる式の途中式であることを宣言するためのものである。セミコロンを末尾に置かない命令文はブロックコードの最終的な評価式として扱われ、その式の結果がブロックコードの外へ戻り値として返される。これは関数定義であれば、結果を返す箇所にはセミコロンをつけない。結果が不要ならばセミコロンによって明示的に捨てるのである。そして結果を返さないならば、それは文と呼ばれる。
「Hello, world!」を標準出力に出力するHello worldプログラム。
階乗を求めるプログラム。if式の結果として1もしくはn * fac_recursive(n-1)を返し、関数の結果としてif式の結果を返す。
変数はletキーワードで定義され、不変(イミュータブル)である。変数への再代入はコンパイルエラーとなる。
可変変数を扱いたい場合、変数宣言時にmutキーワードを利用して可変であることを明示しなければならない。この機能は例えば巨大構造体のコピーを避けながら一部を変更するために有効活用できる。
またRustはシャドーイング(同名変数の複数回定義)に対応している。シャドーイングにより一時的な変数の変更や変数型の変更が可能になる。
変数とは別にconstキーワードで定義される定数を持つ。定数は常に等しい値を持つ(mutやシャドーイングに対応していない)。
この言語の型システムではHaskell言語に倣い「型クラス」を用いることができる。これはアドホックな多相性を容易にするものであり、可変型宣言により実現されるものである。高カインド多相性など、Haskell言語にある他の特徴はサポートされていない。
Rust言語が備える型システムは、impl(実装)、trait(トレイト)、struct(構造体)およびenum(列挙型)を基本として構成される。implが他の言語におけるクラスに近い役割を果たし、継承とポリモーフィズムはtraitによって提供される。traitにはメソッドを定義することができ、traitで宣言されたメソッドはimplによってstructへミックスインされる。structにはフィールドが定義可能で、traitとimplはそれ自身にはフィールドは定義できない。enumには複数種類の型のカテゴリ変数が定義可能で、数値型、文字列型、オブジェクト型などの複数の状態を選択的に持ちうる。菱形継承問題を回避するためにtraitのみが継承が可能である。
変数の型を決定する型システムは静的型付けかつ強い型付けである。静的型付けと動的型付けの区分においての型付けは、コンパイル時に全ての変数に対して型を決定する静的型付けを基本としているが、トレイトをポインタを介して利用することで、ダックタイピングに似た型付けが可能なトレイトオブジェクトが存在する。ほかに、Anyトレイトにて、実行時リフレクションを用いた動的型付けも可能である。強い型付けと弱い型付けの区分においての型付けは、実行時に変数の型を型変換(アップキャスト、ダウンキャスト、クロスキャスト)およびボクシングをすることを許さない強い型付けのみをサポートしている。C言語、Javaは実行時に型変換をすることを許す弱い型付けをサポートしているが、Rust言語ではそのような型変換は認められていない。
Rustコンパイラは変数への代入時(variable = value)、変数の型を値の型に基づき型推論する。変数の宣言には必ずしも型を決定するための初期値を必要としない。変数の宣言時に初期値が与えられた場合は「変数の型」は「初期値の型」であると型推論がなされるが、初期値が与えられなかった場合は以降のブロックコード中のその変数へ値が初めて代入された時に「左辺の変数の型」は「右辺の代入する値の型」であると型推論がなされる。変数への代入が型不一致により失敗した場合にはコンパイル時にエラーを検出する。
ポリモーフィズムを実現するため、構造体のフィールドおよび関数の入出力値は特定のトレイトの実装をするジェネリック型を指定することが出来る。そのような定義の中では、ジェネリック型で型が宣言された変数および入出力値はそのトレイトの特性のみ使用できる。これはジェネリック関数が定義されるとすぐに型判定ができることを意味している。これはC++のダック・タイピングで具体的な型がインスタンス化されるまで判定できないテンプレートとは対照的である。しかし、Rustのジェネリクスの実装はC++のテンプレートの典型的な実装と似ており、インスタンス化ごとにコードの個別のコピーが生成される。これは単相化と呼ばれ、JavaやHaskellで通常使用される型消去方式とは対照的である。単相化の利点は特定のユースケースごとに最適化されたコードであり、欠点は結果のバイナリのコンパイル時間およびサイズが増加することである。Rustのトレイトを用いたポリモーフィズムの実現は、実行時のオーバーヘッドがない「ゼロコスト抽象化」と表現されている。
システムプログラミング言語には効率良いリソース管理機能が必須である(例: メモリ・管理)。C言語はプログラマがリソースを直接管理し高い効率を得られる反面、メモリリークのようなバグ混入の危険性を孕んでいる。より高級な言語(例: Java)ではガベージコレクションのような動的リソース管理機構により高い安全性を得られる反面、動的管理のオーバーヘッドが必ず付きまとう。
Rustは所有権を軸としRAII・参照・借用チェッカーなどを言語仕様として組み入れることで、リソース管理検証をコンパイル時・静的におこなう。ゆえに動的管理のオーバーヘッドを避けつつ、安全性が担保されたリソースアクセスが可能になっている(c.f. ゼロコスト抽象化)。
以下はRustのリソース管理を支える個別の概念である。
所有権(Ownership)はリソース管理の中心を成す重要な概念である。一つのリソースは一つの所有者(変数やブロック)のみに関連付けされるという制約がRust特有のリソース管理機能として存在する。これは、あるリソースの所有権はある所有者(owner)が持っている、のように表現される。同時に、リソースの所有権を複数の変数が持つことは出来ない。
これはC++のスマートポインタの一種であるunique_ptrの振る舞いに似ている。
参照(Reference)は「他者所有値の一時的な借り受け」を表現する値である。すなわち参照とは、値の所有権を元の所有者に置いたまま作成される、値へのアクセス扉である。参照を介して参照先の値を読み書きできる一方、参照を解放しても(所有権がないので)参照先の値は存在し続ける。参照は常に有効な参照先を指すと保証されている。参照を得ることを借用(borrow)という。
参照はいくつかの問題を解決するためにRustへ導入された。まず値へのアクセスを所有者のみに限定した場合、所有者変数がブロック/関数の内外を引き回されて冗長になる。値の所有権を有さずにアクセスを可能にする、扉・プロキシ・エイリアスのようなオブジェクトがあればこれは解決する。しかし単にアクセスオブジェクトを渡すとその有効範囲が不明なためダングリング参照を起こしうる。これは参照と参照先の生存期間等を比較することで静的に検出できる(借用チェッカー)。これにより参照は常に有効になり、ヌル参照チェックも不要になる。このような背景で導入されたのがRustの参照である。
参照に関する型・演算子・式が存在する。借用を表す式を借用式 &expr(borrow expression)という。型・演算子は以下である。
すなわち、演算子 & を用いた式 &expr の評価により型 &T の値が得られる。&mut でも同様である。
共有参照型は参照先の読み取りのみが可能で書き換えができないが、値が不変であるため同じ値を指す複数の共有参照が生成できる。参照はCopy トレイトによりムーブでなくコピーで渡される。可変参照型は両方が可能であるが、値が可変であるため同じ値を指す他の参照が生成できない。ゆえに参照は Copy トレイトを持たずコピーでなくムーブで渡される。
既に解放されたリソースを指し続ける参照はバグを引き起こす(例: ダングリングポインタ)。もし静的にこれを検証できれば、オーバーヘッド無しに安全性を担保できる。また検証により不正な参照を避けられるため参照へのヌル代入が不要となり、ヌルポインタチェックを不要にできる。
Rustでは借用チェッカー(borrow checker、ボローチェッカー)により所有権の競合および不正利用を静的に検証する。所有権が解放されたリソースを指す参照は同時に無効化される。ボローチェッカーは参照についてもライフタイム(lifetimes)としてリソースの生存期間を検証する。これによりヌルポインタやダングリングポインタ、リソース利用競合を制限したメモリ安全性を実現している。
リソースのメモリ確保は基本的にはスタックメモリを利用しており、ヒープメモリを利用したメモリ確保はBoxやVecなどの特定の型のみで限定的に利用されている。
コンパイル時に型のサイズが決定できない可変長配列を表すような型はヒープメモリを利用するBox型を使用してリソースを管理する。Boxリソース自体はスタックメモリで管理されるが、Boxリソースが所有権を持つ実体のリソースはヒープメモリで管理される。標準のヒープメモリ確保にはシステムアロケータを利用するが、対象プラットフォームやビルド時の指定により他のメモリ確保APIを利用することもある。ヒープメモリはスタックメモリに比べて速度性能が落ちるため、必要時にのみ限定的に利用されている。
Rustは実行時に境界チェックをおこなう。これによりバッファオーバーランをはじめとしたメモリアクセスに対する安全性を得ている。この機能はゼロコスト抽象化でなく実行時の命令であるため、安全性と引き換えにいくらかの性能オーバーヘッドが発生している。他のシステムプログラミング言語として代表的なC言語やC++は境界チェックを強制しないため、Rustの特徴の1つとなっている。
Rustのライブラリはクレート(crate)という呼称で提供される。多くのクレートはで公開されている。同サイトはバージョン毎のソースコードをアーカイブとして提供している。クレートは必ずしもcrates.ioに登録されている必要はなく、Webサーバやを指すURIで指定することもできる。ライブラリはソースコードプロジェクトで配布されることが一般的だが、コンパイルしたバイナリライブラリファイル(拡張子rlib)で出力することも可能である。
コアライブラリはcoreの名称で提供されている。このライブラリは標準ライブラリに依存しない基幹ライブラリであり、一切の上位ライブラリ、システムライブラリ、libcライブラリにリンクしていない。コアライブラリは最小限であり、ヒープ割り当てを認識せず並行性や入出力も提供していない。それらのことはプラットフォームへの調整が必要であり、このライブラリはプラットフォームに依存しない。
標準ライブラリはstdの名称で提供されている。このライブラリは基礎的な型(Vec<T>やOption<T>)、言語の基本の処理、標準のマクロ、入出力(I/O)、マルチスレッドなどの機能を提供する。標準ライブラリは標準でリンクしてソフトウェアがビルドされるが、より根幹的なソフトウェアやリソースの小さい組み込み環境ではリンクを外してソフトウェアをビルドすることができる。
Rustは基本的で汎用的な機能を含め標準ライブラリではなく外部ライブラリとして提供している。これはいわゆる「バッテリー同梱 (“Battery Included”)」の反対を行くもので、言語と共に更新することで保守的になりがちな標準ライブラリではなく、言語とは独立して更新することで最善を繰り返し探求しうる外部ライブラリとすることで、それらの機能をより良い品質で提供する考えに基づいていたものである。外部ライブラリの利便性と品質の保証のためにとを提供している。
言語開発のコミュニティがそれらについて携わっていないわけではなく、基礎的な外部ライブラリはコミュニティ配下で開発が進められている。
rand - 乱数生成
regex - 正規表現
chrono - 時刻操作
libc - C言語インターフェース
log - ログ出力インターフェース
Rustの開発ツールは独立したソフトウェアとして提供されているが、Rustの公式なインストール方法に従えば、以下のツール一式が手に入る。
rustcはRustで記述されたRustソースコードをコンパイルするコンパイラ(処理系)である。コンパイルの成果物として中間コード、および実行ファイル、静的ライブラリ、動的ライブラリを出力する。rustcはクロスコンパイルを可能とし、Windows、Linux、macOS向けの実行ファイル他、AndroidやiOSで動作するライブラリをホストマシンで出力することができる。
対象プラットフォームは完成度に合わせて3つのティアに分かれている。ティア1はバイナリリリースが提供されており、自動ビルドと自動テストが整備され、それらが安定して成功(パス)することが保証されている。
ティア2はバイナリリリースが提供されており、自動ビルドと自動テストは整備されているがテストが成功することは保証されていない。ティア3はソースコードとしては対応しているが、自動ビルドと自動テストの動作が保証されておらず、公式リリースは提供されていない。
Windows、Linux、macOS向けのRustコンパイラはティア1でリリースされている。Android、iOS、WebAssembly向けのRustコンパイラはティア2でリリースされている。
Rust 1.12版より導入されたMIR (Mid-level IR) によって、コンパイルと実行時間の迅速化ならびに型チェックの正確性の実現が図られている。
CargoはRust製ソフトウェアプロジェクトのCUIのビルドツールである。規定のファイル構成(ディレクトリ、設定ファイル、ソースファイル)のプロジェクトディレクトリで利用される。プロジェクトのビルド(コンパイル)、依存ライブラリのダウンロード、テスト、ドキュメント生成などをサポートする。通常はCargoを中心に開発を行えるように設計されているため、rustcコンパイラを直接呼び出すことは稀である。Cargoの依存ライブラリのダウンロード先はである。
サブコマンドは拡張可能で標準のコマンドの他、README.mdファイルの自動生成コマンドなどの拡張コマンドが存在する。この拡張はcargo-xxxと名付けたコマンドを適切にインストールするだけで、cargoにxxxというサブコマンドを追加できる。
rustupはツールチェーンの管理ソフトウェアである。ツールチェーンのダウンロードとインストール、ソフトウェアバージョンの管理、コンパイルターゲットの切り替えの機能を提供する。
rustupはRustプロジェクトがリリースしているコンパイラ(rustc)、ビルドツール(Cargo)などのツールチェーンをインターネットを経由してホストマシンにダウンロード、インストールおよびマネージメントする機能を提供する。インストール対象のツールチェーンのバージョンは、安定版、ベータ版、ナイトリー版の全てを含んでおり、利用者が必要とするバージョンを指定してダウンロードすることができる。また、ターゲットプラットフォームについても全ての種類を含んでおり、ホストマシンと異なるプラットフォーム(例えば、ホストがmacOSであってもWindowsやAndroid、iOS)のコンパイラをインストールすることができる。この場合、コンパイラはクロスコンパイラとして動作してターゲットプラットフォームに合わせたビルドを実施する。
Rust 1.13版以前はシェルスクリプトで実装されたrustup.shが公開されていた。これと並行してRust言語で実装された同等機能を提供するrustup.rsの開発が進められており、Rust 1.14版でrustup.shは非推奨となり、Rust 1.14版以降はrustup.rsが公式リリースとして提供されている。
Rustは2016〜2022年に渡ってStack Overflow Developer Surveyで「最も愛されているプログラミング言語」で一位を獲得し続け、プログラマの好意的な評価は多い。
一方で、Rustは学習難易度が高い言語とも考えられている。多くのRust初学者のプログラマにおいて、自身が正しいと思って書いたソースコードのコンパイルに対してコンパイラ(ボローチェッカー)がエラーを検出する、「ボローチェッカーとの戦い」(fighting with the borrow checker)が発生しがちである。小さなアプリケーションの実装について同時期に言語開発されているGo言語と比較した場合、Rust言語よりGo言語の方が開発効率が良いという評価がある。学習難易度の高さは開発チームも認識しており、2017年ロードマップでは学習曲線の改善を目的として挙げていた。
この問題を改善するために、2020年現在において、自動化をメインテーマにした開発を続けており、前述にあるcargo、rustup等のツール以外にも、rustfmt、clippy、cargo docなどのツール類をIDE環境のみならずCUI環境でも利用可能な状態へと整備を続けている。また、後方互換性を維持しているため、crates.ioを用いる事でライブラリ類などの生産性の向上にも努めている。
Rustの実行時速度性能は同じくコンパイラにLLVMを利用している場合のC言語と同等程度であり、一部の処理ではC言語を上回った速度が確認されている。2018年2月時点で、ウェブサーバアプリケーションの汎用処理では速度性能の良いRust製ライブラリは開発が進んでいない。単純なテキスト処理では速度性能は良い。
その後のベンチマークでは、並行処理やグラフィックス処理においても、JavaやGoを上回っており、コンパイラの出力するコードの優秀性が証明されている。ただし、サポートされていないグラフィックスボード(Cのライブラリをインクルードしなければならない)では、C/C++の実効性能には及んでいない。
2006年、Mozillaで働いていたグレイドン・ホアレは現代のインターネット環境で高い並行性と高い安全性が求められるシステムプログラミング言語としてC言語やC++に代わりうるプログラミング言語 Rust言語の開発に着手した。MozillaがRustの開発に関わりはじめたのは2009年で、Rustは2010年のモジラ・サミットで公に姿を表した。RustソースコードをコンパイルするRustコンパイラは、初期の頃はOCaml言語で作られたコンパイラ(rustboot)が用いられていたが、2010年にはRust言語自身でRustコンパイラを作るセルフホスティングへ移行したコンパイラ(rustc)の開発が始められ、翌年にはブートストラップ問題を解決した最初の完動品が完成した。この時よりRustコンパイラはコンパイル基盤にLLVMを用いたものとなった。
プレアルファ(0.1版)と呼ばれる最初のバージョンは2012年1月にリリースされた。Mozillaはこれを新しいモノ好みの人やプログラミング言語愛好家のためのアルファ版と位置づけていた。最初の安定版である1.0版がリリースされるまでの0.x版リリースでは、いくつもの大きな破壊的な仕様変更が言語およびライブラリに加えられた。
変数名や関数名など識別子は、この言語の開発の初期の頃は、ASCII文字以外の文字を使うことが禁じられていた。言語についてのある質疑応答の中で、現場の非英語圏のプログラマーのほとんどが識別子にはASCII文字を使っていると述べられていた。しかしその制限は反論を引き出すことになった。それにより、2011年2月に言語に変更が行われ、この制限は削除された。
型判定は、0.4版以前の従来の静的型付けに加えて、型状態システムをサポートしていた。型状態システムは、特別なcheck文を使用してプログラム文の前後での型決定をモデル化した。C言語やC++コードでのアサーションの場合と同様に、プログラム実行中ではなくコンパイル時に型不一致を検出する。型状態の概念はRust特有のものではなく、NILで採用されていたものである。実際にはほとんど使用されていなかったため型状態は削除されたが、ブランディングパターンで同じ機能を実現できる。
オブジェクト指向のシステムは、0.2版から0.4版にかけて大きく変わっていった。0.2版で初めてクラス(class)が導入され、0.3版でインターフェース(interface)を通したデストラクタとポリモーフィズムが追加され、0.4版で継承を提供する手段としてトレイト(trait)が追加された。インターフェースはトレイトに一部の機能が統合されて、異なる不要な機能として削除された。クラスもまた削除され、構造体とトレイトを使用したミックスインに置き換えられた。
コアのメモリモデルは、0.9版から0.11版の間、2つのビルトインのポインタ型(ヒープメモリ型~とガベージコレクション型@)を機能を単純化するために用意していた。それらは、標準ライブラリのBox型とGc型で置き換えられ、Gc型は最終的には削除された。
2014年1月、Dr. Dobb's Journalの編集長アンドリュー・ビンストックはD言語やGo言語、Nim言語と同様にC++の競争相手になる機会を得たとコメントした。ビンストックによると、Rustは非常にエレガントな言語として広く見られているが、バージョン間で繰り返し大きく変更されたため普及が遅れているという。
2015年5月16日、安定版となるRust 1.0がリリースされた。1.0版の後、安定版およびベータ版が6週間おきに定期リリースされている。またナイトリー版が毎日リリースされている。新たな機能はナイトリー版で開発が進められ、ベータ版で動作が検証されている。
2016年8月2日にリリースされたFirefox 48にはRustで書き直されたメディアパーサが組み込まれており、Mozillaが開発しているブラウザのFirefoxにおいてRustで書かれたコードが書き加えられる初例となった。