最終更新日:2023/10/21 原本2021-07-28

基本からしっかり学ぶRust入門(1):プログラミング言語「Rust」とは? "Hello, World!"で基本を押さえる

Rustはどのようなプログラミング言語なのでしょうか? 本連載のスタートとなる今回は、Rust言語の概略と、手元にRustの動作環境構築までを紹介します。導入で利用可能になるコマンドと、最初のHello, World!プログラムも取り上げます。


連載:基礎からしっかり学ぶRust入門

Mozillaが支援するオープンソースのプログラミング言語「Rust」

 Rust(ラスト)はプログラミング言語としては新鋭で、2006年に開発が始まり、最初の安定版(バージョン1)は2015年に登場しました。以降、概要から見ていきましょう。

画面1 Rustプログラミング言語(Rust Programming Language)
画面1 Rustプログラミング言語(Rust Programming Language)

 Rustは、Webブラウザソフトウェア「Firefox」を開発しているMozillaが支援するオープンソースのプログラミング言語です。2006年に開発がスタートした当初は、Mozilla所属のグレイドン・ホアレ氏の個人プロジェクトでしたが、2009年からはMozilla自体が支援に加わり、公式プロジェクト化されました。MozillaとRustの関係は、Mozillaが2012年に開発を開始したWebレンダリングエンジン「Servo」がRustを用いていることにあります。

 Rustは、度重なる仕様変更を繰り返して、2015年にバージョン1がリリースされました。その後は、後方互換性を重視した6週間隔のリリースサイクルを保持しています。現在は、コミュニティーベースのRust Project Developers主体で開発が進められており、全てのファイルがGitHubで公開されています。

MicrosoftとGoogleが採用

 Microsoftは2019年11月から、同社のコアプラットフォームであるWindowsの開発にRustを採用していることを公表しています。そしてGoogleは、2021年4月に同社のコアプラットフォームであるAndroid OSの開発にRustを採用すると発表しています。さらに、Linuxカーネルの開発にRustを使用する動きも始まっています。

 OSのコア部分の開発は、C言語やC++言語と相場が決まっていましたが、そこにRustが風穴を開けることになりました。その背景には、OSの抱えるセキュリティ上の問題(脆弱《ぜいじゃく》性)の解決があります。セキュリティ上の脆弱性の多くは、メモリ利用におけるバグに起因するものとされていて、C/C++を用いる限りはこれらの言語の特性から克服は困難とされてきました。

 Rustでは、独自の言語設計によりメモリ安全性(メモリ利用における安全性が言語によって保証されていること)を実現しています。MicrosoftやGoogleはメモリ安全性を一番に評価し、Rustの採用を始めているのです。

C/C++に替わるシステムプログラミングのための言語

 このように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では別途テストライブラリをインストールする必要がありませんし、手順も統一しやすくなっています。

モジュールシステムとパッケージマネジャーCargo

 Rustはモジュールシステムを標準で用意しています。モジュールシステムとは、プロジェクトを階層化、分割して管理する仕組みです。ソースファイル1個のプログラムなら問題ありませんが、ソースファイルが多数にわたり、多くのプログラマーがプロジェクトに参加するようになると、プロジェクトを機能や役割で分割して管理する必要があります。

 Rustでは、プロジェクトをパッケージ、クレート、モジュールといった階層で分けて管理できます。言語仕様の中にこれらを利用するための仕組みが用意されているので、特別な外部ツールが必要になりません。Cargoというパッケージマネジャーを使って、これらを適切に管理できます。

 この他にも、マルチパラダイムのプログラミング言語であるなどさまざまな特徴があります。

Rustのインストール

 Rustの概要を紹介したところで、今度は手元にRustの動作環境を整えていきましょう。Rustのインストールは非常に簡単です。基本的には、公式サイト記載の手順に従うだけでインストールは完了します。

 本稿では、macOSとWindows向けにおすすめのインストール手段を紹介します(LinuxについてはmacOSとほぼ同様の手順になります)。

macOSでインストール

 macOSの場合は、Install Rustのページに表示されているコマンドを、ターミナルで実行します(画面2)。

画面2 Rustをインストール
画面2 Rustをインストール

 デフォルトでは、Rustのインストール場所は$HOME/.rustupフォルダ、$HOME/.cargoフォルダになります($HOMEはログインユーザーのホーム。/Users/naoなど)。

 $HOME/.rustupフォルダは、後述するRustのアップデート用フォルダで、メタデータやツールチェーンが置かれます。$HOME/.cargoフォルダが後述するRustのパッケージマネジャーのフォルダで、Rustのコマンド類はbinフォルダに配置されます。

 $HOME/.cargo/binフォルダの環境変数PATHへの設定は、ターミナルを開き直すか、source $HOME/.cargo/envコマンドを実行します。

 インストールするバージョンなどを変更したい場合には「2) Customize installation」を選択しましょう。ここではデフォルトの「1) Proceed with installation(default)」のまま進めました(画面3)。

画面3 インストール方法の選択
画面3 インストール方法の選択

 「Rust is installed now. Great!」と表示されればインストールは完了です(画面4)。

画面4 インストールの完了
画面4 インストールの完了

Windowsでインストール

 Windowsの場合、インストーラ「rustup-init.exe」を利用します(画面5)。インストーラには32bit版と64bit版があります。Windowsのバージョンに合ったものを選びましょう。ここでは64bit版を選択しました。なお、WSL(Windows Subsystem for Linux)用のインストールコマンドも表示されますが、基本的にはmacOS、Linux用と同じです。

画面5 Rustをインストール(Windowsのとき)
画面5 Rustをインストール(Windowsのとき)

 Windowsでインストーラを利用する場合、Microsoft C++ Build toolsのインストールを求められることがあります(画面6)。

画面6 Microsoft C++ Build Toolsがインストールされていないとき
画面6 Microsoft C++ Build Toolsがインストールされていないとき

 Microsoft C++ Build Toolsのインストール方法は2つあります。

 前者の場合、Microsoft Visual C++ Build Toolsから「Build Toolsのダウンロード」をクリックします(画面7)。

画面7 Microsoft Visual C++ Build Tools
画面7 Microsoft Visual C++ Build Tools

 画面6では、「Windows 10 SDK」と「English language pack」を有効にしてインストールします、と表示されています。具体的には、画面7でダウンロードできるVisual Studio Build Toolsのインストーラ(vs_buildtools__1932109249.1589283028.exe。数字はバージョンで変化する)の画面で、[ワークロード]タブが選択されている状態で「C++によるデスクトップ開発」を選択し、右ペインの[インストールの詳細]から「Windows 10 SDK」を選択します(画面8)。

画面8 Visual Studio Build Tools(「ワークロード」タブ)
画面8 Visual Studio Build Tools(「ワークロード」タブ)

 同じ画面の[言語パック]タブで「英語」も選択します(画面9)。

画面9 Visual Studio Build Tools(「言語パック」タブ)
画面9 Visual Studio Build Tools(「言語パック」タブ)

 後者の場合、Visual Studioが不要な場合にはディスク領域とインストール時間を無駄にするので、前者の方法で済ませてしまうのがよいでしょう。筆者も、前者でインストールしました。

 「Microsoft Visual C++ Build Tools」のインストールを済ませたら、画面6で「Continue?」に対して「y」を入力して先に進めましょう。

 デフォルトでは、Rustのインストール場所は%HOME%\.rustupフォルダ、%HOME%\.cargoフォルダになります($HOMEはログインユーザーのホーム。C:\Users\naoなど)。

 %HOME%\.rustupフォルダは、後述するRustのアップデート用フォルダで、メタデータやツールチェーンが置かれます。%HOME%\.cargoフォルダが後述するRustのパッケージマネジャー用フォルダで、Rustのコマンド類はそこのbinフォルダに置かれます。%HOME%\.cargo\binフォルダの環境変数PATHへの設定は、レジストリを書き換えることで自動的に実行されます。

 インストールするバージョンなどを変更したい場合には「2) Customize installation」を選択しましょう。ここではデフォルトの「1) Proceed with installation (default)」のまま進めました(画面10)。

画面10 インストール方法の選択
画面10 インストール方法の選択

 「Rust is installed now. Great!」と表示されたらインストールは完了です(画面11)。[Enter]キーを押してコマンドプロンプトを閉じます。

画面11 インストールの終了
画面11 インストールの終了

Rustのアンインストールと更新

 アンインストールは、macOS、Windowsともにrustup self uninstallコマンドを実行します。「Continue? (y/N)」に対してyで応答すれば、$HOME/.rustupと$HOME/.cargo(macOSなど)あるいは%HOME%\.rustupと%HOME%\.cargo(Windows)からプログラムが除去され、環境変数からも取り除かれます。アップデートがあれば、rustup updateコマンドで更新できます。

% rustup update
info: syncing channel updates for 'stable-x86_64-apple-darwin'
...中略...
  stable-x86_64-apple-darwin updated - rustc 1.53.0 (53cb7b09b 2021-06-17) (from rustc 1.52.1 (9bc8c42bb 2021-05-09))
info: cleaning up downloads & tmp directories
% 

Rustのドキュメント(マニュアル)

 Rustでは、日本語のドキュメントを読むことができます(画面12)。Steve KlabnikとCarol Nicholsの両氏によるドキュメントは、米国のNo Starch Pressよりペーパーバック書籍、電子書籍としても販売されています。

画面 The Rust Programming Language 日本語版
画面12 The Rust Programming Language 日本語版

Rustのプログラム

 Rustをインストールすると、rustup, rustc, cargoという3つの主要なプログラムが$HOME/.cargo/binに用意されます(以降、macOSを基本にして取り上げます)。それぞれ、以下の役割を持っています。

 rustupはRustのツールチェーン(開発に必要なプログラム群)を管理します。既に紹介しましたが、Rustをインストール、アップデートしたりアンインストールしたりするのは、このrustupです。

 rustcはRustのコンパイラです。中間言語、実行バイナリ、静的ライブラリ、動的ライブラリをソースコードから生成するなど、Rustの中核をなします。

 cargoは、Rustのビルドツールです。プロジェクトのビルド、テスト、外部ライブラリのダウンロード、ドキュメントの作成などを行えます。

 コンパイルはrustcで行えますが、将来的にはプロジェクトのビルドという考え方が望ましいため、以降はcargoを中心に取り上げていきます。

最初のプロジェクト

 Rustでは、アプリケーションを「プロジェクト」という単位で管理します。プロジェクトにおけるパッケージの作成、ビルドといった一連の作業はパッケージマネジャーであるcargoで行います。

 なお、本連載では、フォルダ「atmarkit_rust」が任意の場所に作成されているものとして、そこをプロジェクトフォルダとします。ターミナル(macOSの場合)あるいはコマンドプロンプト(Windowsの場合)を開き、そこにcdコマンドで移動しておきましょう。

 それでは早速プロジェクト内にパッケージを作成してみましょう。パッケージは、プロジェクトにおける機能の単位です(本連載のプロジェクト管理の回であらためて取り上げます)。今回作成するのはプログラミング言語入門の定番、「Hello, World!」を表示するパッケージです。

パッケージの作成

 パッケージの作成には、cargoのnewサブコマンドを使います。パッケージの名前は「hello_world」です。Rustでは、パッケージ名やファイル名は全て小文字として、複数の単語から構成される場合には間をアンダースコア(_)で区切ることが推奨されています。コマンドは、上記のatmarkit_rustフォルダで実行します。

% pwd
/Users/nao/Documents/atmarkit_rust
% cargo new --bin hello_world
    Created binary (application) `hello_world` package
%

 実行例はmacOSのターミナル上のものなので、Windowsの場合はプロンプト部分を読み替えてください(%→>など)。ここでは、バイナリ(アプリケーション)hello_worldパッケージが作成されたと表示されます。--binオプションは、バイナリ(実行形式ファイル)を作成することを意味しています(省略時のデフォルト)。今回は触れませんが、--libオプションを与えるとライブラリの作成になります。

ビルドと実行

 これで、cargoコマンドを実行したフォルダに、hello_worldフォルダが作成されます。cdコマンドでそこに移動して、今度はサブコマンドrunを指定してビルド、実行します。

% cd hello_world
% cargo run
   Compiling helloworld v0.1.0 (/Users/nao/Documents/atmarkit_rust/hello_world)
    Finished dev [unoptimized + debuginfo] target(s) in 2.26s
     Running `target/debug/hello_world`
Hello, world!
%

 プログラムを1行も書いていないのに、「Hello, world!」と表示されました。そうです、cargo newではパッケージを作成しますが、ソースファイルのひな型が自動的に作成されます。そのソースファイルに、「Hello, world!」を表示するコードが書かれているというわけです。

ソースコードを確認してみよう

 早速見てみましょう。ソースファイル名は、srcフォルダの中にあるmain.rsです。cargoを用いると、ソースファイルはsrcフォルダに作成されます。

fn main() {
    println!("Hello, world!");
}
リスト01-01:src/main.rs

 これだけです。Rustのソースファイルの拡張子は.rsです。mainがある、printlnがある、ブロックは中かっこ({ })の囲みである、文の終わりにはセミコロン(;)があるなど、C/C++の利用経験がある人なら、内容のイメージは容易でしょう。詳細は本連載の2回目以降で順次取り上げていきます。

 ちなみに、Rustには望ましいとされる書式があります。リスト01-01のように、ブロックは改行せずに始める、インデントは半角スペース4個分である、などです。もっと詳しく知りたいという場合には、rustfmtというRust用フォーマッタにソースファイルをそのまま表示させてみればいいでしょう(上記には挙げませんでしたが標準でインストールされます)。

 バイナリ(実行形式)は、今回の例ではtarget/debugフォルダに作成されます。

% ls target/debug
build                   hello_world             incremental
deps                    hello_world.d
examples                hello_world.dSYM

 hello_worldがバイナリです。Windowsでは、hello_world.exeとなります。このファイルを直接指定して実行できます。他のフォルダやファイルについては、この時点では理解する必要はありませんので省略します。

作成されるファイル

 インストールと動作確認が済んだところで、パッケージの作成に伴って作成されるファイルを確認してみましょう(Windowsではdir /ahコマンドを実行してください)。

% ls -a
.               .git            Cargo.lock      src
..              .gitignore      Cargo.toml      target
%

 .gitと.gitignoreは、Gitリポジトリのためのファイルです。つまり、ここからGitHubにパッケージをプッシュできるわけで、Git連携の準備が整っているのです。Cargo.lockとCargo.tomlは、パッケージのための重要なファイルです。このうち、Cargo.tomlについて見てみましょう(リスト01-02)。

[package]
name = "hello_world"
version = "0.1.0"
authors = ["Yamauchi <nao@naosan.jp>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
リスト01-02:Cargo.toml

 大かっこ([~])で挟まれたセクション、そしてキーと値のエントリで構成される一般的な形式です。[package]セクションでは、文字通りパッケージの情報を保持します。authorsエントリにはパッケージを作成した人の情報がOSから取得されて自動的に入ります。

 下の方にある[dependencies]には依存関係にある別パッケージを記載していきますが、今回はないので有効なエントリは何もありません。このCargo.tomlには、ビルドや実行に関する指示も記述していくことができますが、それらについては本連載の後半の回で取り上げていきます。

 続けて、Cargo.lockも見てみましょう(リスト02-03)。

# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "hello_world"
version = "0.1.0"
リスト01-03:Cargo.lock

 ここにもパッケージ情報のようなものが書かれていますが、注釈にある通りcargoコマンドによって自動的に生成されるので触らないように、とあります。ここでは、このような管理目的のファイルがあるのだという程度の認識にしておきましょう。

 残ったsrcとtargetは、既に説明した通りソースファイルの置き場所、コンパイル結果の置き場所です。targetには、デフォルトではデバッグターゲットのためのdebugフォルダが作成され、そこに成果物が置かれます。cargo runに--releaseオプションを与えるとリリースターゲットとなり、releaseフォルダが作成されてそこに成果物が置かれます。本連載では--releaseオプションは使用せず、デバッグターゲットにてバイナリを作成していきます。

まとめ

 今回は、プログラミング言語Rustの特徴を紹介し、環境を構築して「Hello, World!」を表示させてみました。また、それを通じて、コマンド、フォルダ構成、ファイルの役割などについても触れました。次回は、Rustの言語仕様、変数やデータ型といった基本を解説していきます。