Rust超入門(2)

はじめに

前回、かなり駆け足気味にプログラミング言語Rustの簡単な紹介、開発環境のインストール、Hello Worldプログラムのビルド・実行までをご案内しました。

今回は、もう少しRustのシンタックス・セマンティクスに迫っていきたいと思います!

環境

いつもの実行環境です。

MacBook Pro (Retina, 13-inch, Early 2015)

  • macOS Sierra 10.12.5
  • プロセッサ: 3.1 GHz Intel Core i7
  • メモリ: 16 GB 1867 MHz DDR3
  • rustc: 1.19.0 (0ade33941 2017-07-17)
  • cargo: 0.20.0 (a60d185c8 2017-07-13)

参考

また、この記事は(前回もそうでしたが)公式のドキュメント(内容は少し古い)を参考にして書いてありますので目を通すことをオススメします。

あるいはLearn X in Y minutes where X = Rustもかなり簡易にまとまっているのでオススメです。

では、さっそくはじめましょう!

変数束縛

変数束縛はなんらかの値に名前をつけるものです。たとえば

let x = 42;

とするとこれ以降、42という値をx として参照できます。

println!("{0}", x); // #=> 42

ものすごく乱暴に言えば、他の言語で言うところの変数宣言・定義にあたります。

型アノテーション

Rustは静的型付け言語です。したがって、コンパイル時に型のチェックが行われます。このとき、(先の例のように)わざわざ型を明示せずとも勝手に推論してくれ(る場合があり)ます。この型推論により、型が推論可能ならば省略できるということです。

型を明示するならば

let x: i32 = 42;

のように書きます。Rustがどのような型を提供してくれるのかはあとで紹介します。

可変性

Rustでは変数束縛はデフォルトでイミュータブル(変更不可)です。どういうことかというと、いったんlet x = 42; としたなら、それ以降、新たにx に値を束縛することはできない、ということです。

let x = 42;
x = 999; // コンパイルエラー!

現代的なプログラミングでは安全性や最適化の都合上イミュータブルのほうが有利、とされますよね。

とはいっても、かなりシビアにパフォーマンスが要求される、だのなんだので破壊的な変更が必要になる場合もありますよね。その場合は変数名の前にmut をつけて束縛します。

let mut y = 89;
y = 55; // OK!

ちなみにmut つきで宣言された変数が再束縛されないとき、ミュータブルである必要はない、という内容の警告が出ます。安心安全ですね。

Rustには以下のような型があります。とくに真新しいものはないかもしれません。

プリミティブ

プリミティブとは簡単に言えば組み込みの型です。Rustには以下のような型がプリミティブとして提供されています。

型名 説明
bool 真偽値 trueもしくはfalseのみを取りうる true
char Unicode文字 32ビットのUnicodeのスカラ値 ‘A’ , ‘?’ (Unicode文字ならなんでもOK)
(i|u)(8|16|32|64) 符号付き/符号なし 固定長8/16/32/64ビット整数 i が符号付き、u が符号なし。後ろの数字がビットサイズを表します。 42i32 。整数のデフォルト型), 12_345_678_901_234_567_890u64u64 。接尾辞で型を明示できます。しない場合、この例であればi32 の範囲を超える旨の警告が出されます。数値リテラルは見やすいようにアンダースコア_ で桁を区切ることができます)
(i|u)size 符号付き/符号なし 可変長整数 i が符号付き、u が符号なし。ビットサイズは実行環境のポインタサイズに依存します。 省略
f(32|64) 単精度/倍精度 浮動小数点数 IEEE-745準拠の単精度/倍精度浮動小数点数型 1.5f64 。小数のデフォルト型)

これらの単純な型以外にもプリミティブがあります。

配列

ふつうの配列です。固定長かつイミュータブル(変更不可)で、 単一の型の値のシーケンスを表します。配列の型は[T; N] というものです。T が格納される値の型、N はコンパイル時に決定される、配列の長さ(要素数)です。が、細かいことはいまはおいておきましょう。

配列を作るには

let arr = [1, 2, 3, 4, 5]; // arrの型は[i32; 5]
let mut mrr = [5, 4, 3, 2, 1]; // こうすると変更可能な配列になる。

とします。mut をつけて宣言するとミュータブル(変更可能)な配列を作ることもできます。驚いたことに、イミュータブルなarr もミュータブルなmrr も型は同じ([i32; 5] )です。

各要素へのアクセスはarr[0] のようにして行います。インデックスは0から開始します。

他にもいろいろ言及すべきことはありますが、いったん忘れます。

タプル

Rustではタプルもサポートされています。

タプルは順序付き固定長リスト、ということで配列によく似ていますが、配列と違い、異なる型の値を持つことができます。

たとえば

let t = (123, 3.14);

とすると、t は整数(i32 )と小数(f64 )の値を持ちます。このときのt の型は(i32, f64) です。

要素数1のタプルを作ることもできますが、少し注意が必要です。

let t1 = (89); // #=> t1はi32(!)

(expr) はカッコで囲まれた式、ですので、こう書いてしまうとt1 の型はi32 と推論されてしまいます(ちなみに、「必要のないカッコ」という警告が出ます)。1要素タプルを書くには、

let tt1 = (89, ); // tt1は1要素のタプル。型は(i32,)
let tz = (); // 0要素タプルはこう。型は()

のように、, を含めて書きます。

空のタプル() で書くことができます。これは値を持ちませんが、Unit 型として扱われることがあります。Cでいうvoid のように、関数がとくに意味のある値を返さないことを示すためなどに使われます。

構造体

プリミティブを組み合わせて、より複雑な型を作ることもできます。このような、型を集めてできた型を構造体といいます。構造体の定義は

struct Point {
    x: f64,
    y: f64,
};

のように、struct 構造体の型名 {メンバリスト}という形です。メンバリストはメンバ名: 型名,…… の形式です。Cとは違ってメンバの区切りが, ですので少し注意を。またリストの最後のメンバの, は省略可能です。ちなみにどのスコープでも定義できます。

こうして定義された構造体は

let origin = Point{x: 0f64, y: 0f64};
println!("origin = ({}, {})", origin.x, origin.y); // #=> origin = (0, 0)

として使います。型名 {メンバ名: 値, ……} の形ですね。またメンバへのアクセスはドット形式です。

なお、構造体はすべてのメンバがイミュータブル、あるいはすべてのメンバがミュータブルなものしか定義できません。

let mut some_p = Point{x: 1.5, y: -1.5}; // ぜんぶミュータブル
some_p.x = -3.0; // OK!
some_p.y = 2.2; // OK!
println!("some_p = ({}, {})", some_p.x, some_p.y); // #=> some_p = (-3, 2.2)

のようにmut 束縛によってすべてのメンバをミュータブルにすることはできますが

struct Point {
    mut x: f64, // コンパイルエラー!
    y: f64
};
error: expected identifier, found keyword `mut`
  --> test.rs:20:9
   |
20 |         mut x: f64,
   |         ^^^

一部のメンバだけをミュータブルにすることはできません。

制御構造

関数

入力を受け、なんらかの計算をして、なんらかの結果を返すものを関数といいます。Rustプログラムには必ずただ1つの関数main を持ちます。それはこのような形でしたよね。

fn main() {
    // なんらかの計算……
}

これから、関数はfn 関数名() { 処理内容 } という形であることがわかりますね。

しかし、main 関数の定義だけではわからないこともあります。引数(入力)(の個数とそれぞれの型)と戻り値(出力)(の型)です。これらが必要な関数の定義は

fn some_calc(x: i32) -> i32 {
    x + 1
}

のような形式です。引数リストは仮引数名: 型, …… 、戻り値型は-> に続けて型を指定します。

この関数some_calc は与えられた値に1を加えた値を返す関数ですが、値の返し方はRust独特です。x+1 にセミコロン; がついていないのに気づいたでしょうか。これはこの部分が文ではなくて式であるということを示しています。Rustの関数は、最後に評価された式(の値)を返すのです。

fn some_calc(x: i32) -> i32 {
    x+1;
}

のようにセミコロン; を追加すると……

error[E0308]: mismatched types
 --> test.rs:2:29
  |
2 |   fn some_calc(x: i32) -> i32 {
  |  _____________________________^
3 | |     x+1;
4 | | }
  | |_^ expected i32, found ()
  |
  = note: expected type `i32`
             found type `()`
help: consider removing this semicolon:
 --> test.rs:3:8
  |
3 |     x+1;
  |        ^

宣言ではi32 を返すことになっているが、実際は()が返却されている(≒Cでいうvoid のようになっている)、というエラーです。

この式と文に関してはまた記事を改めたいと思います。

条件分岐

ようするにif のことで、なんらかの条件に応じて処理のパスを選択する制御構造です。

let x = 5;
if x == 3 {
    println!("x = 3");
} else if x == 5 {
    println!("x = 5");
} else {
    println!("それ以外");
} // #=> x = 5

古きよきCスタイルのif-else 文ですね。条件文の周りにはカッコ() は必要ありません(つけることもできますが、不要なカッコの警告が出ます)。else-if 節やelse 節は必要なければ省略できます。

さて、これだけであればそれこそCのif となにも変わらないわけですが、Rustでは動的言語によくあるような式スタイルのif を使うこともできます。

let y = 10;
let z = if y == 9 {
    y + 1
} else if y == 10 {
    y + 2
} else {
    0
};
println!("z = {}", z); // #=> z = 12

繰り返し

プログラミングにおいては繰り返し処理は何度も何度も何度も何度も出てきます。Rustには3つの繰り返し構造用の構文がありますので紹介しましょう。

loop

loop は無限ループのための構文です。

loop {
    println!("Hello Rust World!!");
}

ホントウに無限にHello Rust World!! と言われます。止めたいときにはCtrl-C です。

何らかの条件に応じてループを脱出したい場合にはbreak を使いましょう。

let mut i = 0;
loop {
    println!("Hello Rust World!!");
    i += 1;

    if i == 5 { break; }
}
// #=> Hello Rust World!!
//     Hello Rust World!!
//     Hello Rust World!!
//     Hello Rust World!!
//     Hello Rust World!!

while

while ループは何回ループするかはわからないが、その条件が明確に決まっている場合に使います。

let mut done = false;
let mut cnt = 0;
while !done {
    println!("Hello Rust World!!");
    cnt += 1;

    if cnt == 5 { done = true; }
}
// #=> Hello Rust World!!
//     Hello Rust World!!
//     Hello Rust World!!
//     Hello Rust World!!
//     Hello Rust World!!

例が良くないですね。mut 束縛を2つも使っています。これならloop のほうがよいかもしれません……

なお、無限ループをするつもりなら、while true とするよりも、先に紹介したloop を使いましょう。最適化のかかり具合が違ってくるようです。

for

さて、最後はfor ループです。Cのそれとはかなり異なり、range(範囲)ベースのもののみが提供されます。

let mut sum = 0;
for i in 0..10 {
    sum += i;
}
println!("sum = {}", sum); // #=> sum = 45

for 変数 in 範囲 の形です。範囲Iterable であればなんでもよいです(これに関しては記事を改めましょう)。0..10 とすると[0, 10)の値をイテレートするオブジェクトが作られます。したがって、各ループにおいて、i には0, 1, 2, …, 9 がそれぞれ束縛されるということですね。

そのたのこと

文字列出力

変数の内容や、計算の結果を画面に表示したい!ということはかなり多くあると思います。この記事でもなにも言わずに使っていましたが、Rustでは文字列を表示するにはprint マクロ、あるいはprintln マクロを使います。両者の違いは文字列の最後に改行を出力するかどうか、です。

println!("Hello Rust World!"); // #=> Hello Rust World

マクロの展開には! が必要なのでした(詳細は割愛します)。

では、変数を表示したい場合はどうすればよいのでしょうか。他の多くの言語と同じく、文字列中に値を埋め込む機能(string interpolation: 文字列補完などといいますね)がRustにはあります。そのためには{} を使います。

let x = 89; // xはi32
let y = -3.14 // yはf64
println!("x = {}, y = {}", x, y); // #=> x = 89, y = -3.14

この例からもわかるように複数の{} を使って、複数の値を埋め込むこともできます。さらに、通常は{} が出現する順に引数の値が割り当てられますが、{0} のようにしてどの引数の値を埋め込むか決めることもできます。

println!("y = {1}, x = {0}", x, y) // #=> y = -3.14, x = 89

この例だと、引数はx 、y の順で渡してありますが、表示はy 、x の順になっていることがわかります。このように、引数の順序を変えたり、同じ引数を何度も参照したり、などということが可能です(例えば多言語対応時などに便利かもしれません)。

ところで、同じようにして配列(タプルの場合も同様です)を表示しようとすると……

let arr = [1, 2, 3, 4, 5];
println!("{}", arr); // コンパイルエラー!
error[E0277]: the trait bound `[{integer}; 5]: std::fmt::Display` is not satisfied
 --> test.rs:4:20
  |
4 |     println!("{}", arr);
  |                    ^^^ `[{integer}; 5]` cannot be formatted with the default formatter; try using `:?` instead if you are using a format string
  |
  = help: the trait `std::fmt::Display` is not implemented for `[{integer}; 5]`
  = note: required by `std::fmt::Display::fmt`

というエラーが!要するに、配列を(表示用の)文字列へと変換する方法がわからない、というのです。しかし落ち込む必要はありません。解決策もいっしょに記載されています。曰く、

try using `:?` instead if you are using a format string

「フォーマット文字列を使っているなら、代わりに😕 を試してみて」ということです。やってみましょう。

println!("{:?}", arr); // #=> [1, 2, 3, 4, 5]

なお、引数の位置指定も同時に行うには{0:?} のようにします。

let arr = [1, 2, 3, 4, 5];
let brr = [5, 4, 3, 2, 1];
println!("{1:?}, {0:?}", arr, brr); // #=> [5, 4, 3, 2, 1], [1, 2, 3, 4, 5]

ちなみに、この😕 はDebug トレイトによって文字列へ変換する、という指示です。Debug トレイトやトレイトそのものの解説はここでは省略しますが、細かいことはまだ気にしなくてもいいかもしれません。

命名規則

Rustでは変数名、型名に関してコンパイラがアドバイスをしてくれます。

struct my_values {
    a: i32,
    b: i32,
};
let someValue = 42;

に対して

warning: type `my_values` should have a camel case name such as `MyValues`
  --> test.rs:31:5
   |
31 | /     struct my_values {
32 | |         a: i32,
33 | |         b: i32,
34 | |     };
   | |_____^
   |
   = note: #[warn(non_camel_case_types)] on by default

warning: variable `someValue` should have a snake case name such as `some_value`
  --> test.rs:35:9
   |
35 |     let someValue = 42;
   |         ^^^^^^^^^
   |
   = note: #[warn(non_snake_case)] on by default

と言われます。これから、構造体等の型名はMyValues のように大文字スタートのキャメルケース、変数名はsome_value のような小文字スタートのスネークケース、が推奨されるようですね(ちなみに、いくつか試してみましたが関数に関してはなにも言われません。そのうち公式ドキュメントをあたってみます)。

もちろんこれらは動作には影響を与えませんが、他の人も(おそらく)これに合わせて書くでしょうから、コードを公開する、公開されたコードを読むという場合には合わせておくのが無難でしょう。

おわりに

いかがだったでしょうか。これでRustでの基本的な開発はできるようになったのではないでしょうか。

次回以降(もしあれば)、より進んだ文法や、便利な記法、標準ライブラリ等も扱っていきたいと思います。

お楽しみに。では。