本記事は『バックエンドエンジニアを目指す人のためのRust』(著:安東一慈/大西諒/徳永裕介/中村謙弘/山中雄大)の「第1章 Rustはどういうところで使われているのか?」から抜粋したものです。掲載にあたって編集しています。
Rustとは
Rustは2015年に正式にリリースされたプログラミング言語です。Mozilla社のGraydon Hoare氏の個人プロジェクトから始まり、同社のウェブブラウザであるFirefoxなどに使われました。
Rustは近年注目されている言語であり、プログラミングに関する質問を投稿できる海外サイト「Stack Overflow」で開発者向けに毎年行われる「Developer Survey」においてRustを使いたいと考えているユーザーが多いという調査結果が出ています。また、Rustを使っているユーザーのうち「来年も使いたいと考えている」割合が80%を超えています。
では、Rustはなぜ使いたいと思われているのでしょうか? 理由としては大きく3つあります。
- パフォーマンスの高さ
- 安全性の高さ
- 生産性の高さ
それぞれについて解説していきます。
Rustは高パフォーマンス
Rustはプログラムのメモリ効率や実行速度を非常に重視した言語です。さまざまなプログラミング言語の実行速度を比較した「Benchmark Games」というウェブサイトによると、Rustはほかのプログラミング言語と比べてトップクラスの実行速度であることがわかります。
では、なぜこれほど実行速度が速いのでしょうか。
機械語にコンパイルされる
人間が読んで理解できるソースコードを、コンピュータ上で実行するための形式に変換することを、コンパイルと言います。コンパイルを行うプログラムをコンパイラと呼びます。
JavaやPythonといったプログラミング言語は、バイトコードという形式にコンパイルされます。バイトコードはハードウェア上で直接実行することができず、仮想マシンというソフトウェア上で実行することができます。仮想マシンはバイトコードを、ハードウェア上で実行するための機械語に変換する必要があるため、機械語を直接実行するのに比べて遅くなってしまいます。一方で、機械語はOSやCPUが異なる環境では実行できませんが、バイトコードは仮想マシンが動く環境であればOSやCPUが異なっていても実行できるというメリットがあります。
Rustを始め、CやC++、Goといったプログラミング言語は、機械語にコンパイルされます。バイトコードを経由する場合と異なり、環境が変わってもそのまま動くというメリットはありませんが、機械語はハードウェア上で直接実行されるため、高速に実行することができます。
ガベージコレクションが不要である
Go言語をはじめとする多くのプログラミング言語には、ガベージコレクションという、プログラム上で使われていないメモリ領域を解放する仕組みがあります。ガベージコレクションにより、プログラマはメモリの確保や解放に気を使う必要がほとんどなくなります。一方で、ガベージコレクションのためにメモリ領域の使用状況を定期的にチェックする必要があるため、全体の実行速度が少し下がってしまいます。
RustやC、C++といったプログラミング言語にはガベージコレクションは搭載されておらず、プログラマが自分で気をつけてメモリ領域を確保したり、解放したりする必要があります。CやC++ではメモリ領域の扱いが難しく、バグの原因になっていましたが、Rustでは所有権という概念を導入し、プログラマが意識しなくてもガベージコレクションなしで自然にメモリの管理ができるようになっています。
ゼロコスト抽象化を実現している
プログラミングをしていると、数値や文字列など、異なる種類のデータに対して同じ処理を実装するケースがあります。同様のコードをデータの各種類に対して実装するのではなく、1つのコードで各種類について動くようにすることを、抽象化と言います。抽象化されたコードは、各種類のデータを処理するために、それぞれの種類にあった実際の処理に変換する必要があります。
Javaなどのプログラミング言語は、抽象化されたコードを実行時に変換しているため、実行が少し遅くなってしまいます。Rustでは、コンパイル時に抽象化されたコードを実際の処理に変換しているため、実行時には変換済みの処理を使うことができ、実行速度に影響しません。これをゼロコスト抽象化と呼びます。
Rustでは、このゼロコスト抽象化により、抽象化によるプログラミングのしやすさと実行速度を両立しています。
Rustは安全性が高い
プログラマは安全なプログラムを書くように気をつける必要があります。しかし、気をつけていても混入してしまう間違いとして、次に示すようなバグがよく知られています。
- 一度解放したメモリ領域を参照してしまう
- 複数のスレッドから同時に同じ場所に書き込んでしまう
これらのバグは古くから存在し、今なおソフトウェア開発の現場でプログラマの悩みの種となっています。
Rustでは、これらの点について、前述した所有権という概念を使ってコンパイル時に機械的にチェックしてくれるため、プログラマがうっかり混入させてしまうのを防いでくれます。
Rustは生産性が高い
バックエンドエンジニアに求められることは、次に示すように多岐にわたります。
- 開発を効率よく進めるために、ほかの人が作ったツールを取り入れる
- テストコードを書いて、自分の書いたコードが想定どおり動くことを保証する
- チームでコーディング規約を決め、それに沿ってコードを書く
Rustには、これらをサポートする機能やツールが組み込まれています。
パッケージマネージャ
プログラミングをする際に、ゼロからすべて自分でコードを書くこともできますが、ほかのプログラマが書いたコードを取り入れて使うこともできます。このように、ほかの人が書いたコードをライブラリと呼びます。
Rustではライブラリを使いやすくパッケージングして配布しています。パッケージされたライブラリはcrate(クレート)と呼ばれていて、https://crates.io/で世界中のプログラマが自分で作ったcrateを公開しています。各crateにはライセンス(規約)が定められていて、ライセンスの範囲内で自由に使うことができます。crateを自作して公開する方法は第7章で詳しく解説します。
パッケージされたライブラリを簡単に利用するツールは、パッケージマネージャと呼ばれています。Rustでcrateを簡単に使うためのパッケージマネージャとしてcargoが用意されています。cargoを使ってcrateを追加、削除、更新することができます。
ユニットテスト
ソフトウェア開発をするうえで、テストは欠かせません。
テストの方法はいくつかあります。手動でプログラムを動かし、動作に問題がないことを確認することもあれば、テストコードと呼ばれるテストをするためのプログラムを書いて、プログラムに機械的にテストをさせることもあります。
コードを追加するたびに手動でテストをするのは手間ですし、時間もかかります。また、人間が行う以上、テストすべき項目が抜けてしまうこともあります。テストコードを書いて、プログラムに機械的にテストをさせることで、動作を担保しつつ高速に開発を進めることができます。
ほかのプログラミング言語では、テストコードを書くために特別な準備が必要な場合もありますが、Rustではテストコードを書く仕組みが標準で備わっています。
プログラムのコードとテストコードを同じファイルに書くことができるため、動作の内容と担保されている挙動を同時に確認することができます。
pub fn add(a: i32, b: i32) -> i32 { a + b } #[cfg(test)] mod tests { use super::*; #[test] fn test_add() { assert_eq!(add(1, 2), 3); } }
フォーマッター、リンター
チームで開発するときにはコーディング規約というものを決めることが一般的です。コーディング規約とは、プログラムを書くときの取り決めで、複数人からなるチームで開発していても、同一人物が書いたかのような一貫性のあるコードを書くためのルールです。 Rustにはrustfmtというコードを自動的に整形するツール(フォーマッター)が標準で搭載されています。改行の位置やスペースの位置などが、コーディング規約に沿ったスタイルに機械的に整えられ、複数人で開発していても同じスタイルでコードを書くことができます。
また、clippyというコーディング規約を守っているかチェックするツール(リンター)も標準で備わっています。リンターを定期的に実行することで、コーディング規約に沿っていないコードがいつの間にか交ざってしまうことを防げます。
豊かな表現力
Rustはさまざまな状態をプログラムに落とし込むことができる豊かな表現力があります。
例えば、RustにはOption型というものが標準で備わっていて、これを使うことで値がない可能性があるということを表現できます。ほかのプログラミング言語では、nullという値を用いて値がないことを表現しますが、これは値があるかどうかをプログラマ自身が気をつける必要があります。値があると思っていたが実行してみると値がないこともあった、ということも往々にしてあり、バグの原因になっています。
Rustではnullは採用されず、Option型を用いることで、値がない場所で値を読み込もうとし ないようにコンパイラが機械的にチェックしています。
fn main() { let value: Option= Some(1); // let value: Option = None; // 値がない場合はNoneと表現する match value { Some(_) => println!("値があります"), None => println!("値がありません"), } }
Option型を用いることで、値がないケースの見落としを機械的にチェックすることができ、バグを未然に防ぐことができます。
また、Result型というものも標準で備わっています。これは、エラーが発生する可能性があるということを表現できます。
fn main() { let value: Result= Ok(1); // let value: Result = Err("error"); // Errでエラーを表現する match value { Ok(_) => println!("OKです"), Err(_) => println!("エラーです"), } }
ほかのプログラミング言語では例外という形でエラーを扱うことがありますが、プログラマ自身でエラーが発生するかどうか、気をつける必要があります。RustではResult型を用いることで、エラーを処理し忘れていないかを機械的にチェックしています。これによって、エラー処理を忘れたことでプログラムが強制的に停止してしまうことを防いでいます。
どこで活用されているか
ここまでで、Rustは高パフォーマンスで、安全性、生産性がいずれも高いプログラミング言語であることがわかりました。Rustはこれらの特徴を活かして、著名なソフトウェアの開発に使われています。
例えば、Dropboxは2016年という早い段階から、内部で動作しているファイルシステムの開発にRustを採用しています。当時はGo言語も候補に挙がったそうですが、RustのほうがGo言語よりも効率的にデータを管理できることから、Rustが採用されています。
また、WindowsやAndroidといったOSの開発にもRustが使われはじめています。従来、OSの開発にはC言語やC++が使われることがほとんどでした。しかし、これらの言語は高パフォーマンスであるもののメモリ管理をプログラマ自身が気をつけて行う必要があり、OSのバグや脆弱性の原因となっていました。そこで、C言語やC++と同等の性能が出て、かつ、安全性の高いRustに置き換えることで、バグや脆弱性を減らして品質を高める動きが進んでいます。
これらのソフトウェア以外にも、Rustはハードウェアへの組み込み用途からウェブアプリケーション、ゲームまでさまざまな場面で幅広く活用されています。
活発なコミュニティ活動
Rustの開発はGitHubというウェブサイトで行われていて、毎日のように新しい機能が追加されています。公式フォーラムでも活発に議論されているほか、RustConfというイベントが毎年開催され、多くの人が集まっています。
日本でも活発にコミュニティ活動が行われています。
- Rustのドキュメント翻訳まとめ:Rustのドキュメントを有志の方々が翻訳しています
- Rust.Tokyo:日本で行われるRustのカンファレンスで、毎年多くの参加者が集まります
- rust-jp:日本語で質問できるオンラインのチャットで、誰でも参加できます
このように日本語で参加できるコミュニティも活発ですので、ぜひRustの世界に飛び込んでみてください!