Rustはどのようなプログラミング言語なのでしょうか? 本連載のスタートとなる今回は、Rust言語の概略と、手元にRustの動作環境構築までを紹介します。導入で利用可能になるコマンドと、最初のHello, World!プログラムも取り上げます。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
Rust(ラスト)はプログラミング言語としては新鋭で、2006年に開発が始まり、最初の安定版(バージョン1)は2015年に登場しました。以降、概要から見ていきましょう。
Rustは、Webブラウザソフトウェア「Firefox」を開発しているMozillaが支援するオープンソースのプログラミング言語です。2006年に開発がスタートした当初は、Mozilla所属のグレイドン・ホアレ氏の個人プロジェクトでしたが、2009年からはMozilla自体が支援に加わり、公式プロジェクト化されました。MozillaとRustの関係は、Mozillaが2012年に開発を開始したWebレンダリングエンジン「Servo」がRustを用いていることにあります。
Rustは、度重なる仕様変更を繰り返して、2015年にバージョン1がリリースされました。その後は、後方互換性を重視した6週間隔のリリースサイクルを保持しています。現在は、コミュニティーベースのRust Project Developers主体で開発が進められており、全てのファイルがGitHubで公開されています。
Microsoftは2019年11月から、同社のコアプラットフォームであるWindowsの開発にRustを採用していることを公表しています。そしてGoogleは、2021年4月に同社のコアプラットフォームであるAndroid OSの開発にRustを採用すると発表しています。さらに、Linuxカーネルの開発にRustを使用する動きも始まっています。
OSのコア部分の開発は、C言語やC++言語と相場が決まっていましたが、そこにRustが風穴を開けることになりました。その背景には、OSの抱えるセキュリティ上の問題(脆弱《ぜいじゃく》性)の解決があります。セキュリティ上の脆弱性の多くは、メモリ利用におけるバグに起因するものとされていて、C/C++を用いる限りはこれらの言語の特性から克服は困難とされてきました。
Rustでは、独自の言語設計によりメモリ安全性(メモリ利用における安全性が言語によって保証されていること)を実現しています。MicrosoftやGoogleはメモリ安全性を一番に評価し、Rustの採用を始めているのです。
このようにRustは、C/C++が使われてきた、あるいはこれから使われるであろう場面で、C/C++に替わる有効な選択肢となってきています。
その理由の第一が、Rustがネイティブコンパイラ言語ならではのコンパクトさと高速性を持ち、かつメモリなどのリソースの細やかな取り回しが可能なプログラミング言語である点です。しかし、単に軽量、高速で細やかなリソース操作が可能であるというだけでは、C/C++にとって替わる意味がありせん。
Rustでは、C/C++の弱点といわれる低い並列性、ポインタに代表される危険性を克服しています(※1)。つまり、高い安全性を保ちながら、並列性を担保し、細かなリソース操作も可能にしたプログラミング言語、それがRustなのです。
※1 メモリ安全性を犠牲にして効率を重視したUnsafe Rustという言語仕様もRustには含まれています。これを利用すると、C/C++のようなメモリ操作が可能になりますが、本連載では触れない予定です。
C/C++は設計が古いこともあって(Cに至っては誕生50年になろうとしています)、当時のCPUやメモリの能力を最大限に引き出すために効率を優先、安全性を二の次にしてきたようないきさつがあります。
分かりやすいのがポインタで、ポインタによる効率的なメモリ操作を可能にする一方、初期化されていない、有効なオブジェクトを指さないポインタ「ダングリングポインタ」や、確保したメモリを解放しないまま残り、メモリリソース枯渇の原因となる「メモリリーク」などによるプログラムの欠陥の原因になるなどの問題を生じさせてきました。
ネイティブコンパイラ言語では、動的にポインタの有効性などをチェックするのは性能とのトレードオフの関係にあり、一般的には行われていません。JavaやC#といった中間言語型の言語では動的なチェックを行っていますが、性能的には不利になります。
Rustでは、コンパイル時にメモリ安全性を確保するためのボローチェッカーを備えています。ボローチェッカーとは、メモリなどのリソースの所有者とリソースの生存期間(ライフタイム)の静的解析をする仕組みのことです。Rustでは、リソースと所有者を1対1にするという制約を設けることで、ある変数がオブジェクトを所有するとして、そのオブジェクトは他の変数では所有できないとし、変数の消滅とオブジェクトの破棄というライフタイムを管理できます。
ライフタイムを管理できるのでオブジェクトの破棄タイミングをコンパイラが把握でき、不要になったタイミングですぐに破棄できます。これはGC(ガベージコレクタ)が不要ということであり、GC特有の、「プログラマーからするとオブジェクトの破棄タイミングが分からない、コントロールできない」という問題と無縁になります。
このようにRustはメモリ安全性を備えるプログラミング言語なのですが、同様にメモリ安全性を備えるJavaやC#はどうかというと、これらの言語はインタープリタが必要な中間言語型であり、OSのコア部分の開発といったシステムプログラミングには適しません。システムプログラミングには、ターゲットCPUが直接解釈できる機械語に変換できるネイティブコンパイラ言語であることが求められるのです。
Rustのコンパイラは、Clangと同様にLLVM(※2)と呼ばれる仮想コンパイラプラットフォームに基づいたコンパイル機構に準拠しており、LLVMの中間コードにいったん変換されたあと、最終的にターゲットとなるCPUで動作可能なバイナリを生成します。Javaの仮想マシンと異なるのは、Javaが実行時に中間言語を解釈するのに対し、Rustは実行時には既にCPUが直接実行可能なバイナリになっている点です。
これらから、Rustのバイナリはコンパクトで、Cに匹敵する、あるいは上回る速度で動作し、システムプログラミングに適したプログラミング言語となっています。
※2 以前は、Low Level Virtual Machine(低水準仮想機械)の略とされていましたが、現在は単に(何の頭文字でもない)LLVMとされています。
C/C++は、スレッドセーフでないことがたびたび問題視されます(設計時にはスレッドという概念がなかったためです)。Cの一部の標準ライブラリ関数ではstatic(静的)な変数を使っているため、複数のスレッドから関数を呼び出すことで競合が発生する可能性があります。また変数をロックして競合の問題を回避したとしても、今度はロック解除待ちが相互に発生するデッドロックの問題もあります。
2021年現在はpthreadsといったPOSIX準拠のマルチスレッド対応ライブラリの登場で状況は変わっていますが、外部ライブラリに頼らざるを得ない状況は変わりません。Rustでは、標準ライブラリにマルチスレッドの機能が用意されているので、スレッドセーフが保証されています。
これまでに登場したプログラミング言語とRustについて、主要な項目で比較したのが表1です。
主要項目 | Rust | C/C++ | Java/C# |
---|---|---|---|
ネイティブコンパイル | ○ | ○ | × |
メモリ安全性 | ○ | × | ○ |
スレッドセーフ | ○ | △ | ○ |
ガベージコレクタ | 不要 | ― | 必要 |
表1 RustとC/C++、Java/C#の比較 |
大まかに、テストには、関数単位でテストを行う単体テストと、アプリケーション全体をテストする結合テストがあります。Rustでは、標準でこれら双方の自動テスト機能が用意されています。
Javaの場合、テストのための外部ライブラリ「JUnit」が有名ですが、Rustでは別途テストライブラリをインストールする必要がありませんし、手順も統一しやすくなっています。
Rustはモジュールシステムを標準で用意しています。モジュールシステムとは、プロジェクトを階層化、分割して管理する仕組みです。ソースファイル1個のプログラムなら問題ありませんが、ソースファイルが多数にわたり、多くのプログラマーがプロジェクトに参加するようになると、プロジェクトを機能や役割で分割して管理する必要があります。
Rustでは、プロジェクトをパッケージ、クレート、モジュールといった階層で分けて管理できます。言語仕様の中にこれらを利用するための仕組みが用意されているので、特別な外部ツールが必要になりません。Cargoというパッケージマネジャーを使って、これらを適切に管理できます。
この他にも、マルチパラダイムのプログラミング言語であるなどさまざまな特徴があります。
Copyright © ITmedia, Inc. All Rights Reserved.