目次


Rust 入門ガイド

Rust の紹介

C と C++ に代わるコンパイル言語

Comments

コンテンツシリーズ

このコンテンツは全#シリーズのパート#です: Rust 入門ガイド

このシリーズの続きに乞うご期待。

このコンテンツはシリーズの一部分です:Rust 入門ガイド

このシリーズの続きに乞うご期待。

ソフトウェアを作成したことが一度でもあれば、間違いなく「どの言語で作成するのがよいだろうか」と考えたことでしょう。もっともな疑問です。可能な限り処理速度の速いコードにする必要があるのか、コードを Web 上で実行するのか、バックエンド上で使うのか、フロントエンド上で使うのか、などの点から、使用すべき言語が決まってきます。すべての言語には、それぞれ得意分野があるからです。それは、Rust にしても同じことです。

静的型付け型言語である Rust は、ほとんどのユーザーが C または C++ に任せている役割を受け持ちます。けれども CC++ とは異なり、Rust は C# と Java 言語が今世紀の大半にわたって支配してきた領域も得意としています。Rust はメモリー安全性を確保する言語であり、オペレーティング・システムに依存しないため、どのコンピューター上でも実行できます。基本的には、C# や Java でのように煩わしいガーベッジ・コレクションを行うことなく、システム言語の処理速度と低レベルでのメリットをすべて手に入れることができるのです。ワクワクしませんか?それは私も同じです。Rust の世界へようこそ!

: この記事に記載するコードとコマンドはいずれも Linux で稼働するコンピューター上で作成しましたが、macOS 用にも Windows 用にも簡単に変換できます。

Rust をインストールして実行する

ほとんどの入門ガイドではすぐに本題のコードの詳細に入りますが、それよりも、ソフトウェアをコンパイルして実行する方法を把握してから開発を開始したほうが効果的だと思います。このリンク先の Rust のインストール・ノートに、Rust をインストールするために必要な手順が説明されています。この手順はどのオペレーティング・システムでも同じです。

インストールが完了したら、サンプル・コードを保管するプロジェクトをセットアップします。私は自分でプロジェクトのディレクトリー構造を作りましたが、本来なら、Rust に同梱されている Cargo という名前の強力なパッケージ・マネージャーを利用したいところです。このリンク先の Cargo マニュアルを読めば、簡単に Cargo を使って新しい実行可能バイナリー・プログラムを作成できます。それには、端末で以下のコマンドを実行すればよいだけです。

$ cd ~/Documents
$ cargo new rusty_start –bin

コマンド cargo new rusty_start は Cargo に対し、rusty_start という名前の新しいディレクトリー構造を作成するよう指示します。--bin オプションを指定して、Cargo に実行可能バイナリーとしてプログラムを作成させます。このオプションを省略すると、Cargo はライブラリーを作成します。基本的に、main.rs ファイルを lib.rs で置き換えるのは簡単です。tree プログラム内に新しく作成された rusty_start ディレクトリーを調べると、以下のようになっています。

$ cd rusty_start
$ tree .
.
??? Cargo.toml
??? src
    ??? main.rs

完璧です!けれども、Cargo.toml ファイルは一体何なのでしょう?このファイルは、Cargo がプログラムをビルドするために使用します。現時点では、このファイルは単にプログラムの作成者とプログラムの依存関係を記述するにすぎません。この例の場合、私が唯一の作成者なので、外部のパッケージは不要です。

main.rs 内には、Cargo によってコードも生成されていることに注目してください。

fn main() {
    println!("Hello, world!");
}

Rust では上記のようにして main 関数を定義します。とりあえず覚えておく点として、この関数による出力先は端末です。ここで疑問となるのは、このコードを実行する方法です。実際のところ、このコードは簡単に実行できます (リスト 1) 。

リスト 1. Rust での Hello World!
$ cargo build
    Compiling …
     Finished …
$ cargo run
     Finished …
      Running …
Hello, world!

java や CMake のセットアップと比べると、まるで手間がかからないことは確かです。もっと簡単にコードを実行するとしたら、cargo run を実行するという方法もあります。このコマンドは、コードのコンパイルと実行の両方に対処します。

Rust をインストールして実行できるようになったので、次は、Rust 本体に移ります。

Rust のコア機能

このセクションでは、以降のセクションが遥かに理解しやすくなるよう、2 つのトピックを取り上げます。それは、変数のバインドと関数です。

バインド内で不変にする

事前ビルドされた Hello, World! プログラムとは異なり、最終的には Rust コード内で変数を使用しなければならなくなります。変数を名前にバインドするために、main.rs ファイル内でキーワード let を使用します。

fn main() {
    let x = 5;
}

上記のスニペットでは、変数 x に値 5 をバインドしています。一方、型についてはどうでしょう?前に Rust は静的型付け型言語であると言ったことを思い出してください。Rust の長所の 1 つは、コンパイル時に値から変数の型を推測できることです。つまり、変数の型という非常に慎重に扱うべき点に、実行時に注意を払わなくても済むようになります。明示的に型を宣言するとしたら、例えば 32 ビットの符号付き整数の場合、以下のように簡単に型を宣言できます。

let x: i32 = 5;

いくつかのプリミティブ型から型を選択することもできます。これについては、次のセクションで説明します。

関数に話題を移す前に、Rust での let キーワードを使用したバインドの便利で有用な側面を紹介させてください。それは、let では以下のようなパターンを使用できることです。

let (x, y) = (5, ‘6’); // types (i32, char)

上記では、変数 x に整数 5 をバインドし、変数 y に文字 6 をバインドしています。この手法は極めて有用で、Rust コミュニティーでは、こうしたパターンを至るところで目にするはずです (ちなみに、Rust コミュニティーのメンバーは「Rustacean」と呼ばれています)。パターンを使用してさらに複雑なバインドに対処することもできますが、簡潔にするために、上記の例だけにとどめておきます。

デフォルトでは、Rust のあらゆる要素は不変です。つまり、後から変更することは不可能です。例えば、以下のコマンドをコンパイルするとします。

let x: i32 = 5;
x = 6;

この場合、コンパイラーは失敗し、x は可変ではないというエラーを返してきます。この変数を変更するには、mut キーワードを使用しなければなりません。

let mut x: i32 = 5;
x = 6;

これで、エラーは出されなくなります。

変数について最後に触れておきたい側面は、スコープです。スコープに込められている概念は、変数が存在できるのは、その変数がインスタンス化された特定の波括弧 ({}) 内に限られるというものです。スコープの外部から変数にアクセスしようとすると、コンパイルが失敗します。一例として、リスト 2 のコードを見てください。

リスト 2. 変数のスコープを設定する
fn main() {
    let x: i32 = 5; // x lives in this scope

    {
        let y: i32 = 4; // y only live in this scope.
    }
  
    println!(“x: {}, y: {}, x, y); // y is not in this scope
}

Rust に備わっている所有権と借用という特性に関して言えば、スコープを理解することが重要となります。次は、関数の話題に移ります。

使用できる関数

変数は実に素晴らしい要素ですが、変数に付随するパートナー、つまり関数がなければ使いものになりません。あらゆるプログラムでは例外なく、少なくとも 1 つの関数を使用します。Rust でそれに該当するのは、main 関数です。main 以外の関数を新しく定義するには、fn キーワードを使用します。

fn foo() {
}

引数を取るにはどうすればよいでしょうか?それは簡単なことです。

fn print_value(value: i32) {
    println!(“The value given was: {}”, value);
}

上記の関数は整数の引数を取り、その引数の値を出力します。リスト 3 に、この関数を main.rs 内で使用する方法を示します。

リスト 3. 引数の値を出力する
fn main() {
    print_value(17);
}

fn print_value(value: i32) {
    println!(“The value given was: {}”, value);
}

let を使用して変数を割り当てる場合と同様に、引数も、名前の後にセミコロン (;) を続けて、その後に型を続けるというパターンに従います。複数の引数を割り当てるには、変数をコンマ (,) で区切ればよいだけです。

fn print_values(value_1: i32, value_2: i32) {
    println!(“Values given were: {} and {}”, value_1, value_2);
}

これで、引数を取る関数と取らない関数のいずれも作成できるようになったので、値を返す関数を作成してみましょう。以下の関数は、整数を 1 ずつ増やしていきます。

fn increase_by_one(value: i32) -> i32 {
    value += 1
}

Rust では、関数が返す値は 1 つだけです。その値の型は、矢印 (->) の後に宣言します。i32 型の値はどこで返すのでしょうか?Rust の面白い点として、セミコロンを省略して値を返すことが一般的な慣例になっています (この言語では return キーワードも使用できますが、それほど頻繁に使用されるわけではありません)。return キーワードを使用するとしたら、以下のように関数を作成します。

fn divmod(x: i32, y: i32) -> (i32, i32) {
    return (x / y, x % y);
}

以上で、変数をバインドする方法と関数を作成する方法の基本を説明しました。前述のとおり、Rust で処理する型はたくさんあるので、次のセクションで要約します。

使用する型

他のあらゆるプログラミング言語と同様に、Rust でも型システムを採用しています。ここからは、Rust で使用する型を簡単に紹介します。

ブール型

ブール型について説明することはそれほどありません。Rust の組み込みブール型 (bool) では、truefalse の 2 つの値を使用します。以下に一例を示します。

let x = true;
let y: bool = !x; // y == false

文字型

bool 型と同じく、Rust には単一の Unicode のスカラー値に対応する型 (char) があります。ただし、ほとんどの言語とは異なり、char は数値を格納する型ではありません。char 型を初期化するには、以下のように単一引用符を使用します。

let x = ‘x’;
let y: char = ‘y’;

文字列型

Rust は特殊な言語であり、strSrting という 2 つの文字列型があります。str 型は「固定サイズ」なので、それほど興味深い点はありません。ただし、先頭にアンパーサンド (&) を付けた &str 型は、かなり役に立ちます。簡単に言うと、&str 型は str への参照を使用します (このトピックは、次のセクションで取り上げます)。もう一方の String 型は、ヒープが割り当てられたバージョンの &str 型です。

まず、&str について詳しく説明します。この型は、「文字列スライス」と呼ばれます。この型はサイズが固定された、不変の UTF-8 バイトのシーケンスです。この型を変数にバインドするには、二重引用符を使用します。

let x = “Hello, World!”;
let y: &str = “Isn’t it a wonderful life?!”;

String 型オブジェクトは可変です。この型を初期化するには、String::from コンストラクターを使用します。

let x = String::from(“Hello, World”);
let y: String = String::from(“Isn’t it a wonderful life?!”);

数値型

Rust での数値型には、あらゆる符号とサイズのバリエーションがあります。現在使用されている数値型は以下のとおりです。

  • i8
  • i16
  • i32
  • i64
  • u8
  • u16
  • u32
  • u64
  • isize
  • usize
  • f32
  • f64

i で始まる数値型は、符号付き整数を表します。つまり、負の数値にすることもできます。u で始まる数値型は、符号なし整数を表します。f で始まる数値型は、浮動小数点値を表します。数値型の名前に使われている数値は、その型のバイト・サイズを表します。名前に単語 size が使われている数値型は、システムのポインター・サイズを表し、シーケンシャル配列の要素にアクセスする場合に使用されます。

配列型

配列型に関しては、まず、Rust コミュニティーのメンバーたちがデータのシーケンスをどのように表現するかについて説明しておかなければなりません。基本的な戦略としては、以下のように配列を使用して表現します。

let x = [1, 2, 3];
let y: [i32; 3] = [4, 5, 6];

明示的に配列の型を宣言する場合の式は [T; N] です。ここで、T は要素の型を表し、N は要素の数を表します。他の言語での配列と同様に、Rust で配列の要素にアクセスするには大括弧 ([]) を使用します。ただし、Rust の配列では len (配列に含まれる要素の数) などの組み込み関数も使用できます。

let x = [1, 2, 3];
let s: usize = x.len(); // s == 3
let e = x[1]; // e is an i32, and e == 2;

配列は面白そうに見えますが、頻繁には使用されません。Rust コミュニティーの大半では、配列ではなくベクトルを使用しています。配列とは異なり、ベクトルはデータをヒープ上に割り当てます。この違いは、&strString の違いによく似ています。ベクトルでデータのシーケンスを作成するには、マクロ vec! を使用し、データの型を Vec<T> として指定します。ここで、T はベクトルに含まれる要素の型です。

let x = vec![1, 2, 3];
let y: Vec<i32> = [4, 5, 6];

タプル型

最後に取り上げる基本的な Rust の型は「タプル」です。タプルは順序付けられたオブジェクトのリストであり、このリストを変更することはできません。タプル型の利点は、オブジェクトの型を統一させる必要がないことです。

let x = (5, ‘6’);
let y: (i32, char) = (7, ‘8’);

タプル型には、配列とは少々異なる方法でインデックスを付ける必要があります。

let x = (5, ‘6’);
let y: i32 = x.0; // y == 5
let z: char = x.1; // z == ‘6’

例えば、単一の値からなるタプルを作成する場合、最初の要素の後にコンマを追加します。こうしないと、括弧が無視されてしまうからです。

let x: (bool) = (true,); x == (true)
let y: bool = (false); y == false

フロー制御

重宝な制御キーワード一式が Rust に用意されていなかったとしたら、Rust でコードを作成してプログラミングするのは相当困難な作業になっていたはずです。最も役立つ 2 つのキーワードを紹介しましょう。

if

Rust は他のほとんどのプログラミング言語と似ているので、リスト 4 に示す Rust の if ステートメントの例には見覚えがあるはずです。

リスト 4. Rust での if ステートメント
let x = ‘5’;

if x == ‘5’ {
    println!(“X is the char ‘5’”);
}

This can also be extended with the other keywords else if and else:

let x = ‘5’;

if x == ‘5’ {
    println!(“X is the char ‘5’!”);
} else if x == ‘6’ {
    println!(“X is the char ‘6’!”);
} else {
    println!(“I don’t know what X is.”);
}

他の 2 つのキーワード、else ifelse を使って拡張することもできます。

if ブロックを三項式のように使うこともできます。

let x = ‘5’;
let y = if x == ‘5’ { 5 } else if x == ‘6’ { 6 } else { -1 };
// y == 5

上記のコードが機能するわけは、等号記号 (=) の左辺にある情報が、関数のように値を返す式であるためです。if ブロック内では、整数にセミコロンが使用されないことに注意してください。

ループ

ループ処理を行う手段がなかったとしたら、Rust は大した言語にはならなかったでしょう。この言語には、ループ処理の手段として loopforwhile の 3 つがあります。まず、loop から見ていきましょう。loop は、単に、永遠にループ処理を続けるためのキーワードです。

loop {
    println!(“Looping for eternity!”);
}

私と同じようだとすれば、いつでも永遠にループ処理を続けたいわけではないでしょう。Rust でも、その場合は一般的な while ループを使用して、特定の条件でループ処理を中断することができます。

リスト 5. Rust での while ループ
let mut x = 0;

while x != 5 {
    println!(“x: {}”, x);
    x += 1; // this is equal to x = x + 1;
}

リスト 6 に、loopbreak キーワードを使用して、特定の時点でループ処理を中断する方法を示します。

リスト 6. Rust で break キーワードを使用したループを作成する
let mut x = 0;

loop {
    if x == 5 {
        break;
    }

    println!(“x: {}”, x);
    x += 1;
}

最後に、Rust では for ループも使用できます。他のループとは異なり、for ループは他のほとんどの C スタイルの言語で使用している以下の形式を使用しません。

for(int x = 0; x < 5; x++) {
    printf(“%d\n”, x);
}

Rust では、リスト 7 に記載する形式を使用します。

リスト 7. 標準的な Rust の for ループ (方法 1)
for x in 0..5 {
    println!(“x: {}”, x);
}

または、一般的にはリスト 8 の形式がよく使われます。

リスト 8. 標準的な Rust の for ループ (方法 2)
for var in expression {
    …
}

上記の例では、expression をイテレーターに変換できます。リスト 7 の例では、0..5 が、最初の値で開始して最後の値で終了するイテレーターになりますが、ループに最後の値は含まれません。0..5 は「範囲」と呼ばれます。Rust は標準的な C スタイルのフォーマットに従っていないため、範囲を使用することによって、ループの各要素を処理する際によくある間違いを回避できます。しかも、リスト 9 のように、ベクトルなどのオブジェクトで Pythonスタイルの繰り返し処理を行うこともできます。

リスト 9. Rust での繰り返し処理
let x = vec![0, 1, 2, 3, 4];

for element in &x {
    println!(“element: {}”, element);
}

// prints out the elements in x: 0, 1, 2, 3, 4

&x は、for ループがベクトルを「借用」していることを意味します。これが、次のセクションで説明する概念です。

所有者から借用する

疑いようもなく、借用は、この記事で説明する最も重要なセクションであり、概して Rust で最も重要な概念です。それと同時に、新しいユーザーにとって、借用は最も理解に苦しむ概念でもあります。けれども Rust はこのシステムによって、C と同等の処理速度を発揮し、しかも C より遥かに安全な言語となっているのです。

所有権

Rust では、すべての変数が、その変数にバインドされた値に対して所有権を持ちます。変数がスコープを外れると、Rust はそのリソースをクリーンアップします。以下の例を見てください。

fn foo() {
    let x = String::from(“Hello, World!”);
}

x 変数が foo 関数のスコープ内で作成されると、この文字列型変数に代入されたデータが、ヒープ内で割り当てられます。foo 関数が終了すると、x 変数はスコープから外れるため、Rust は x 変数に関連付けられた文字列データをクリーンアップします。この仕様は、そのバイトがヒープ内にあるとしても例外ではありません。C/C++ では free または delete を間違いなく含める必要がありますが、この所有権の仕組みにより、その苦痛から解放されます。

所有権を理解するのに役立つもう 1 つの方法は、移動セマンティクスです。すべての変数が自身にバインドされた値に対して所有権を持つというだけでなく、Rust はすべてのリソースが単一の値にバインドされることを保証します。別の言葉に置き換えると、ある変数から別の変数にリソースが再割り当てされると、元の変数は無効になるということです (リスト 10)。

リスト 10. Rust での移動セマンティクス
fn main() {
    let x = String::from(“Hello, World!”);
    
    println!(“{}”, x); // x is valid here

    let y = x; // the resource assigned to x is move to y
    
    println!(“{}”, x);
}

このコードを実行するとプログラムが失敗し、コンパイラーから、y 変数にリソースの所有権が移動された x 変数を使おうとしたと通知されます。この動作は関数にも当てはまります (リスト 11)。

リスト 11. Rust の関数での移動セマンティクス
fn main() {
    let x = vec![1, 2, 3];

    take_ownership(x);

    println!(“{:?}”, x); // ignore the ‘:?’ for now.
                         // it just prints the full vector.
}

fn take_ownership(v: Vec<i32>) {
   println!(“I took this data: {:?}”, v);
}

このプログラムも失敗します。コンパイラーからは、take_ownership 関数内で y 変数にリソースの所有権が移動された x 変数を使おうとしたと通知されます。この問題を修正するには、リスト 12 に示すようにコードを作成します。

リスト 12. コンパイラー・エラーを修正する
fn main() {
    let x = vec![1, 2, 3];

    let x = print_vector(x);

    println!(“{:?}”, x);
}

fn print_vector(v: Vec<i32>) -> Vec<i32> {
   println!(“I took this data: {:?} , and returned it”, v);
   v
}

けれども、ばかげていると思えませんか?ベクトル内の要素を集計して、その値を返したい場合を考えてみてください。元のベクトルのタプルとその合計を返す必要が本当にあるでしょうか?いいえ、その必要はありません。このような場合のために、借用の概念があるのです。借用のトピックに移る前に、所有権に対する以下の 3 つのルールを覚えておいてください。

  • Rust では、値ごとに、値の所有者と呼ばれる変数があります。
  • 値の所有者は、常に 1 つだけです。
  • 値の所有者がスコープから外れると、その値は破棄されます。

参照と借用

所有者とは何かについて明確に定義したので、別の同じく重要な概念である借用について詳しく掘り下げて説明します。Rust では、リソースの借用に対して以下の 3 つのルールが適用されます。

  • 元の所有者がスコープから外れると、借用は無効になります。
  • 不変の借用 (「不変の参照」) は、随時、1 つのリソースに対していくつでも保持できます。
  • 可変の借用 (「可変の参照」) は、随時、1 つのリソースに対して 1 つしか保持できません。

注意すべき点として、ルール 2 とルール 3 に同時に準拠することはできません

所有権」サブセクションの最後で、オブジェクト所有権と関数について、意地の悪い難題を投げ掛けました (リスト 13 を参照)。

リスト 13. オブジェクト所有権についての難題
fn main() {
    let x = vec![1, 2, 3];

    let x = print_vector(x);

    println!(“{:?}”, x);
}

fn print_vector(v: &Vec<i32>) -> Vec<i32> {
   println!(“I took this data: {:?} , and returned it”, v);
   v
}

上記のコードでは、元のリソースを常に元の所有者に返す必要があります。このような機能は慣用的な Rust とは言えません。一方、参照と借用システムを組み合わせて使用すれば、毎回リソースを返さなくても、同じ目標を達成できます (リスト 14 を参照)。

リスト 14. 参照を使用して借用する
fn main() {
    let x = vec![1, 2, 3];

    print_vector(&x);

    println!(“{:?}”, x);
}

fn print_vector(v: &Vec<i32>) {
   println!(“I borrowed this data: {:?}”, v);
}

リスト 14 のプログラムは、問題なくコンパイルされます。x の先頭にアンパーサンドを追加してから print_vectorprint_vector の引数に渡すことによって、大きな問題を解決したのです。慣用的に言うと、print_vectorx を借用していることになります。ここで、値 5x にプッシュする関数を作成するとしたら、どうすればよいでしょうか?それには、m を可変にする必要があります (リスト 15 を参照)。

リスト 15. m を可変にする
fn main() {
    let mut x = vec![1, 2, 3];

    push_five(&x);

    println!(“{:?}”, x);
}

fn push_five(v: &Vec<i32>) {
    v.push(5);
}

結果はどうなるかと言うと、ビルドが失敗して、コンパイラー・エラーになります。失敗の原因は、print_five に渡す引数が可変でないためです。この引数はリソースへの不変の参照となっているため、可変の参照に変更します (リスト 16 を参照)。

リスト 16.可変のビルドを修正する
fn main() {
    let mut x = vec![1, 2, 3];

    push_five(&x);

    println!(“{:?}”, x);
}

fn push_five(v: &mut Vec<i32>) {
    v.push(5);
}

これでもまだ、コンパイル・エラーになります。それは、x への不変の参照を print_five に渡しているに過ぎないためです。該当する以下の行を変更します。

push_five(&mut x);

これでやっと、コンパイル・エラーがなくなります。次は、3 つのルールを破ることができるかどうか調べてみましょう。まずは、ルール 1 を以下のように破ります (リスト 17)。

リスト 17. Rust のルール 1 を破る
fn main() {
    let x = 4;
    let mut x_ref: &i32 = &x;

    // this works, and prints “x_ref: 4”
    println!(“x_ref: {}”, x_ref);

    {
        let y = 5;
        x_ref = &y;
    }

    // this, however, will fail, according to rule 1!
    println!(“x_ref: {}”, x_ref);
}

x_ref を使用する時点で、借用した値 y が無効になっているため、x_ref を使用できないというエラーをコンパイラーから受け取ります。これは非常に役立つ通知です。C ではこのような間違いを何度も犯してき ましたが、それに気付くこともありませんでした。続いて、ルール 2 とルール 3 には同時に準拠できないことを確認します (リスト 18)。

リスト 18. Rust のルール 2 を破る
fn main() {
    let mut x = 4; // needs to be mut to borrow mutable.
    let x_ref_1 = &x;
    let x_ref_2: &i32 = &x; // this is fine because we can have 
                            // multiple immutable references.

    let x_mut_ref = &mut x; // this is not fine because it
                            // breaks the rule 2 and 3 mutual
                            // exclusivity.
}

成功です!コンパイラーから、x の可変の参照と不変の参照を同時に使用することはできないと警告するエラーが返されます。最後に、ルール 3 を破ってみます (リスト 19)。

リスト 19.Rust のルール 3 を破る
fn main() {
    let mut x = 4;
    let x_mut_ref_1 = &x;
    let x_mut_ref_2: &mut i32 = &x;
}

エラーです!またしてもルールを破りました。確かに、3 つのルールはしっかりと適用されているようです。これには理由があります。不変の変数で、参照によってリソースを変更できるようにすると、大量のバグが発生してしまうからです。このように、Rust は実に楽しいと同時に、安全でもある言語です。

オブジェクト

このセクションでは、Rust でオブジェクトを作成する方法を説明します。このオブジェクト指向の世界においては、オブジェクトを作成することが必要不可欠のようです。

構造体

大半の言語とは異なり、Rust にはクラスがありません。Rust では、クラスではなく、データだけを保持する構造体を使用します。以下に、Rust での標準的な構造体を示します。

struct Circle {
    center: (i32, i32),
    radius: u32,
}

この構造体を分解し、キーワード struct を使用してカスタム構造体を宣言します。メンバー変数を追加するには、variable_name という形のキーと値からなる構文を使用します。複数のメンバー変数を追加する場合は、各メンバー変数をコンマで区切ります。関数と同じように、宣言を終了するのにセミコロンを使用する必要はありません。リスト 20 に、このようにして宣言した構造体を使用する方法を示します。

リスト 20. Rust で構造体を使用するための構文
fn main() {
    let c_1 = Circle {
        center: (0, 0),
        radius: 1,
    };

    let c_2: Circle = Circle {
        center: (-1, 1),
        radius: 2,
    };

    println!(“c_1’s radius is {}”, c_1.radius);
}

struct Circle {
    center: (i32, i32),
    radius: u32,
}

まず、impl キーワードによって、以下の関数に特定の型 (この例では Circle) を関連付けるよう Rust に指示します。次に、Rust での慣例に従って、コンストラクターに「new」という名前を付けます。この new 関数は、「関連関数」と呼ばれています。つまり、リスト 20 で Circle::new とあるように、型を使用して関数を呼び出します。

構文を理解したところで、メソッドをいくつか追加しましょう。リスト 21 を見てください。

リスト 21. Rust でのメソッドの例
fn main() {
    let mut c = Circle::new((0, 0), 1);

    println!(“The circle’s area is {}”, c.area());
    println!(“The circles location is {:?}”, c.center);
    
    c.move_to((-1, 1));
    
    println!(“The circles location is {:?}”, c.center);}

struct Circle {
    center: (i32, i32),
    radius: u32,
}

impl Circle {
    fn new(center: (i32, i32), radius: u32) -> Circle {
        Circle {
            center: center,
            radius: radius,
        }
    }
    
    fn area(&self) -> f64 {
        // converting self.radius, a u32, to an f64.
        let f_radius = self.radius as f64;
        
        f_radius * f_radius * 3.14159
    }
    
    fn move_to(&mut self, new_center: (i32, i32)) {
        self.center = new_center;
    }
}

メソッドを追加するのは簡単なようですが、新しく登場した self 引数は何なのでしょう?この引数は、ドット構文 (リスト 21 の main 関数内に示されています) を使用してメソッドに渡されます。引数の値は、メソッドを呼び出したオブジェクトです。

Rust でのメソッドには、self&self&mut self という固有の 3 つの変数があります。所有権と借用に関するあれこれを踏まえると、これらの変数はそれぞれ、メソッドへのオブジェクト所有権の移動、オブジェクトの不変の借用、オブジェクトの可変の借用に対処するものです。

これで、他のプログラミング言語で使用されている基本的なオブジェクトと同じように、メソッドとコンストラクターを持つ構造体を手に入れました。まだ取り上げていない型は、列挙型だけです。

列挙型

enum は、変化する状態を表す型です。C/C++ の多くのユーザーはプログラムの特定の状態を示すために列挙型を多用するため、enum の登場を願ってもいないことだと思うでしょう。以下に、enum キーワードを使用して列挙型を作成する方法を示します。

enum Color {
    Red,
    Green,
    Blue,
}

struct の宣言と妙に似通っているように見えますが、バリアントには型が割り当てられていません。この新しい enum では、リスト 22 に示すように型の変数を割り当てることができます。

リスト 22. 変数を割り当てる
fn main() {
    let red = Color::Red;
    let blue: Color = Color::Blue;
}

enum Color {
    Red,
    Green,
    Blue,
}

Rust では、例えば関連するデータをバリアントに追加するなど、enum 型を使用してユニークな処理を行えるようになっていますが、それについてはこの記事の範囲ではありません。

その他の機能

ここまでのところで、Rust プロジェクトを開始するために必要になる可能性のあるすべての要素について説明しました。このセクションでは、コーディングを円滑に進める上で役立つ、いくつかの細かい点に触れておきます。

match

Rust で作成されたプログラムを調べると、match というキーワードが頻繁に出現します。switch/case に置き換わる単純な要素として捉えられたり、if/else ブロックでも対処できると考えられたりする場合がありますが、いずれも誤りです。条件が複雑になってくると、if/else ブロックはかなりの大きさになって収拾がつかなくなってきます。また、CC++ で使われている switch/case ブロックに至っては (デフォルトの case を入れ忘れると) エラーだらけになってしまいます。リスト 23 に、match の単純な例を記載します。

リスト 23. match の単純な例
fn main() {
    let x = 5;
    
    match x {
        1 => println!(“Matched to 1!”),
        2 => println!(“Matched to 2!”),
        3 => println!(“Matched to 3!”),
        4 => println!(“Matched to 4!”),
        5 => println!(“Matched to 5!”),
        6 => println!(“Matched to 6!”),
        _ => println!(“Matched to some other number!”),
    };
}

match の仕組みを説明すると、match が式 (x) を取り、その式の値と一致するブランチを見つけるというものです。match ステートメントのそれぞれの「腕」は、value => expression という形を取り、コロンで区切られます。適切な腕が見つかると、対応するブランチの式が実行されます。_ 値は、「その他」または「デフォルト」を表す Rust の構文です。

match に伴う特殊な側面として、このキーワードを使用する場合、特定の式が一致する可能性のある結果のすべてを包括的に網羅しなければなりません。別の言葉に置き換えると、_ ブランチを削除したとすると、ビルドが失敗し、コンパイラーから、考えられる i32 型の値 (つまり、-2,147,483,648 から 2,147,483,647 までの値) がすべて揃っていないと通知するエラーが返されます。

match の面白い使い方は、リスト 24 に示すように、これを enum で使用することです。

リスト 24. enum で match を使用する
fn main() {
    let color = Color::Red;

    match color {
        Color::Red => println!(“Got a red color!”),
        Color::Green => println!(“Got a green color!”),
        Color::Blue => println!(“Got a blue color!”),
    };
}

enum Color {
    Red,
    Green,
    Blue,
}

match は式であるため、前に三項式で使用した if ブロックと同じように、変数を割り当てるために使用できます。

リスト 25. match を使用して変数を割り当てる
let x = 5;
    
let s = match x {
    1 => “Matched to 1!”,
    2 => “Matched to 2!”,
    3 => “Matched to 3!”,
    4 => “Matched to 4!”,
    5 => “Matched to 5!”,
    6 => “Matched to 6!”,
    _ => “Matched to some other number!”,
};

println!(“{}”, s);

モジュール

私がまだ名前空間について触れていないことを心配していることでしょう。名前空間について触れていないのは、Rust では名前空間を使用しないためです。Rust では名前空間ではなく、mod キーワードを指定して作成したモジュールを使用します。モジュールは、リスト 26 のように定義します。

リスト 26. モジュールを定義する
fn main() {
    let c = Circle {
        center: (0, 0),
        radius: 1,
    };
}

mod shapes {
    struct Circle {
        center: (i32, i32),
        radius: u32,
    }
}

上記では、mod shapes ブロック内に、前に定義した Circle 構造体を含めています。これは、Circle がこの名前空間に属するようになったことを意味します。したがって、このプログラムをコンパイルするとビルドが失敗します。コンパイラーは、Circle 型が main スコープ内で定義されていないと警告してきます。この問題を修正するために、モジュールの名前の後に 2 つのコロンを続けます (リスト 27)。

リスト 27. Circle を定義する
fn main() {
    let c = shapes::Circle {
        center: (0, 0),
        radius: 1,
    };
}

mod shapes {
    struct Circle {
        center: (i32, i32),
        radius: u32,
    }
}

これでもビルドは失敗しますが、今回は、コンパイラーから Ciscle 構造体がプライベートであると通知されます。このエラーは一般的なエラーです。なぜなら、Rust では、モジュール内の型、変数、関数はすべてデフォルトでプライベートに設定されるためです。この動作を無効にするには、pub キーワードを使用する必要があります。リスト 28 に、Circle 宣言を修正する方法を示します。

リスト 28. Circle 宣言を修正する
mod shapes {
    pub struct Circle {
        center: (i32, i32),
        radius: u32,
    }
}

上記のコードによって Circle のプライバシー問題は修正されますが、今度はメンバー変数の centerradius がプライベートであるという警告が出されます。このエラーは以下のようにして修正します。

mod shapes {
    pub struct Circle {
        pub center: (i32, i32),
        pub radius: u32,
    }
}

ようやく、プログラムがコンパイルされました。けれどもこれでは、ある質問をはぐらかしていることになります。その質問とは、このモジュールを本当に main.rs ファイル内に組み込みたいのかというものです。その答えは「ノー」です。私はモジュールを別個のファイル内に格納するほうを望みます。そこで、main.rs が置かれているのと同じ src フォルダー内に shapes.rs ファイルを新規に作成します。このファイル内に、Circle の新しい宣言を挿入します。リスト 29 に、新しいファイルとその内容を記載します。

リスト 29. 新しい shapes.rs
main.rs:
fn main() {
    let c = shapes::Circle {
        center: (0, 0),
        radius: 1,
    };
}

shapes.rs:
pub struct Circle {
    pub center: (i32, i32),
    pub radius: u32,
}

これで、shapes の内容が shapes.rs に移されましたが、Circle にアクセスするにはどうすればよいのでしょうか?それは簡単です。mod キーワードを使用して特定の名前のファイルを見つければ、それらのファイルを名前空間として使用できます。リスト 30 に、新しい main.rs ファイルの内容を記載します。

リスト 30. main.rs の内容
mod shapes;

fn main() {
    let c = shapes::Circle {
        center: (0, 0),
        radius: 1,
    };
}

基本的に、上記の mod ステートメントは Rust に対し、shapes.rs ファイル、または mod.rs ファイルが含まれている shapes フォルダーのいずれかを見つけるように指示しています。毎回 shapes::Circle を使用するのが面倒な場合、Rust では便利な技を使えます。それは、use キーワードです。リスト 31 に、このキーワードを使用する方法を示します。

リスト 31. use キーワード
mod shapes;

use shapes::Circle;

fn main() {
    let c = Circle {
        center: (0, 0),
        radius: 1,
    };
}

これで、Circle がこのスコープ内で宣言されているかのように、この構造体を使用できるようになります。リスト 32 では、さらに高度な use の使い方を確認できます。ここでの目的は、(Rust の std::collections モジュール内にある) ハッシュマップを使用することです。

リスト 32. ハッシュマップを使用する
use std::collections::HashMap;

fn main() {
    let mut counter = HashMap::new(); // HashMap<char, i32>

    counter.insert(‘a’, 1);
}

チュートリアルの完了まで、あとわずかです。最後に、サード・パーティー製パッケージを使用する方法を説明します。Rust では、サード・パーティー製パッケージは「クレイト」と呼ばれます。

クレイト

自分で設定した数値の変数をどのように作成するのかはわかりますが、ランダム変数が必要な場合はどうすればよいでしょうか?Rust の資料に目を通してみたところ、標準的なランダム変数の実装が見つかりません。そこで、サード・パーティー製ライブラリーを使用することにしました。Google で検索すると、すぐに rand というクレイトが見つかります。

rand を使用するには、まず、Cargo.toml ファイルをリスト 33 に記載するコードに変更する必要があります。

リスト 33. rand を使用して変更した Cargo.toml
[package]
name = "rusty_start"
version = "0.1.0"
authors = ["Dylan Hicks <dirtgrub.dylanhicks@gmail.com>"]

[dependencies]
rand = "0.4"

rand = "0.4" を依存関係に追加して、Cargo に対し、この名前とバージョンのクレイトを検索して、そのクレイトをバイナリーにコンパイルするよう指示します。main.rs ファイル内では、extern crate キーワードを使用しました。

extern crate rand:

fn main() {
    let x = 5;
}

これで、リスト 24 に示すように use キーワードの助けを借りて、rand クレイトによって乱数を生成できるようになりました。

リスト 34. rand クレイトを使用して乱数を生成する
extern crate rand:

use rand::Rng;

fn main() { 
    let mut rng = rand::thread_rng(); // random number generator
    let x = rng.get::<i32>(); // ignore the ::<i32>
                              // this just tells the rng to make
                              // an i32.

    println!(“x: {}”, x);
}

最後のまとめ

ついにチュートリアルの最後にまでたどり着きました!ただし、この記事では Rust の概要をすべて説明しきれませんでした。システム言語を必要とする新しいプロジェクトを開始しようとしていたり、使用する言語として Java や C# が候補に挙がっていたりする場合は、Rust を代替手段として使用することを検討してください。Rust は処理速度に優れていて、読んで理解しやすいだけでなく、他のどの言語よりも安全なので、人間のエラーが最小限になります。詳細を調べるには、https://doc.rust-lang.org/book にアクセスして、Mozilla が作成した、この素晴らしい言語に関する両方の書籍を読んでください。


ダウンロード可能なリソース


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source
ArticleID=1063511
ArticleTitle=Rust 入門ガイド: Rust の紹介
publish-date=11222018