変数束縛

事実上全ての「Hello World」でないRustのプログラムは 変数束縛 を使っています。 変数束縛は何らかの値を名前へと束縛するので、後でその値を使えます。 このように、 let が束縛を導入するのに使われています。

訳注: 普通、束縛というときは名前 と束縛しますが、このドキュメントでは逆になっています。 Rustでは他の言語と違って1つの値に対して1つの名前が対応するのであえてこう書いてるのかもしれません。

fn main() { let x = 5; }
fn main() {
    let x = 5;
}

例で毎回 fn main() { と書くのは長ったらしいのでこれ以後は省略します。 もし試しながら読んでいるのならそのまま書くのではなくちゃんと main() 関数の中身を編集するようにしてください。そうしないとエラーになります。

パターン

多くの言語では変数束縛は 変数 と呼ばれるでしょうが、Rustの変数束縛は多少皮を被せてあります。 例えば、 let の左側の式は「パターン」であって、ただの変数名ではありません。 これはこのようなことが出来るということです。

fn main() { let (x, y) = (1, 2); }
let (x, y) = (1, 2);

パターン式が評価されたあと、 x は1になり、 y は2になります。 パターンは本当に強力で、本書にはパターンのセクションもあります。 今のところこの機能は必要ないので頭の片隅に留めておいてだけいて下さい。

型アノテーション

Rustは静的な型付言語であり、前もって型を与えておいて、それがコンパイル時に検査されます。 じゃあなぜ最初の例はコンパイルが通るのでしょう?ええと、Rustには「型推論」と呼ばれるものがあります。 型推論が型が何であるか判断出来るなら、型を書く必要はなくなります。

書きたいなら型を書くことも出来ます。型はコロン(:)のあとに書きます。

fn main() { let x: i32 = 5; }
let x: i32 = 5;

これをクラスのみんなに聞こえるように声に出して読むなら、「 x は型 i32 を持つ束縛で、値は である。」となります。

この場合 x を32bit符号付き整数として表現することを選びました。 Rustには多くのプリミティブな整数型があります。プリミティブな整数型は符号付き型は i 、符号無し型は u から始まります。 整数型として可能なサイズは8、16、32、64ビットです。

以後の例では型はコメントで注釈することにします。 先の例はこのようになります。

fn main() { let x = 5; // x: i32 }
fn main() {
    let x = 5; // x: i32
}

この注釈と let の時に使う記法の類似性に留意して下さい。 このようなコメントを書くのはRust的ではありませんが、時折理解の手助けのためにRustが推論する型をコメントで注釈します。

可変性

デフォルトで、 束縛は イミュータブル です。このコードのコンパイルは通りません。

fn main() { let x = 5; x = 10; }
let x = 5;
x = 10;

次のようなエラーが出ます。

error: re-assignment of immutable variable `x`
     x = 10;
     ^~~~~~~

訳注: エラー: イミュータブルな変数 `x` に再代入しています

束縛をミュータブルにしたいなら、mutが使えます。

fn main() { let mut x = 5; // mut x: i32 x = 10; }
let mut x = 5; // mut x: i32
x = 10;

束縛がデフォルトでイミュータブルであるのは複合的な理由によるものですが、Rustの主要な焦点、安全性の一環だと考えることが出来ます。 もし mut を忘れたらコンパイラが捕捉して、変更するつもりでなかったものを変更した旨を教えてくれます。 束縛がデフォルトでミュータブルだったらコンパイラはこれを捕捉できません。 もし 本当に 変更を意図していたのなら話は簡単です。 mut をつけ加えればいいのです。

可能な時にはミュータブルを避けた方が良い理由は他にもあるのですがそれはこのガイドの範囲を越えています。 一般に、明示的な変更は避けられることが多いのでRustでもそうした方が良いのです。 しかし変更が本当に必要なこともあるという意味で、厳禁という訳ではないのです。

束縛を初期化する

Rustの束縛はもう1つ他の言語と異る点があります。束縛を使う前に値で初期化されている必要があるのです。

試してみましょう。 src/main.rs をいじってこのようにしてみて下さい。

fn main() { let x: i32; println!("Hello world!"); }
fn main() {
    let x: i32;

    println!("Hello world!");
}

コマンドラインで cargo build を使ってビルド出来ます。 警告が出ますが、それでもまだ「Hello, world!」は印字されます。

   Compiling hello_world v0.0.1 (file:///home/you/projects/hello_world)
src/main.rs:2:9: 2:10 warning: unused variable: `x`, #[warn(unused_variable)]
   on by default
src/main.rs:2     let x: i32;
                      ^

Rustは一度も使われない変数について警告を出しますが、一度も使われないので人畜無害です。 ところがこの x を使おうとすると事は一変します。やってみましょう。 プログラムをこのように変更して下さい。

fn main() { let x: i32; println!("The value of x is: {}", x); }
fn main() {
    let x: i32;

    println!("The value of x is: {}", x);
}

そしてビルドしてみて下さい。このようなエラーが出る筈です。

$ cargo build
   Compiling hello_world v0.0.1 (file:///home/you/projects/hello_world)
src/main.rs:4:39: 4:40 error: use of possibly uninitialized variable: `x`
src/main.rs:4     println!("The value of x is: {}", x);
                                                    ^
note: in expansion of format_args!
<std macros>:2:23: 2:77 note: expansion site
<std macros>:1:1: 3:2 note: in expansion of println!
src/main.rs:4:5: 4:42 note: expansion site
error: aborting due to previous error
Could not compile `hello_world`.

Rustでは未初期化の値を使うことは許されていません。 次に、 println! に追加したものについて話しましょう。

印字する文字列に2つの波括弧({}、口髭という人もいます…(訳注: 海外の顔文字は横になっているので首を傾けて { を眺めてみて下さい。また、日本語だと「中括弧」と呼ぶ人もいますね))を入れました。 Rustはこれを何かの値を入れて(interpolate、インターポーレート)くれという要求だと解釈します。 文字列インターポーレーション (String interpolation)はコンピュータサイエンスの用語で、「文字列の中に差し込む」という意味です。 その後に続けてカンマ、そして x を置いて x がインターポーレートしようとしている値だと指示しています。 カンマは2つ以上の引数を関数やマクロに渡す時に使われます。

単に波括弧だけを使った時は、Rustはインターポーレートされる値の型を調べて意味のある方法で表示しようとします。 フォーマットをさらに詳しく指定したいなら数多くのオプションが利用出来ます。 とりあえずのところ、デフォルトに従いましょう。整数の印字はそれほど複雑ではありません。

スコープとシャドーイング

束縛に話を戻しましょう。変数束縛にはスコープがあります。変数束縛は定義されたブロック内でしか有効でありません。 ブロックは {} に囲まれた文の集まりです。関数定義もブロックです! 以下の例では異なるブロックで有効な2つの変数束縛、 xy を定義しています。 xfn main() {} ブロックの中でアクセス可能ですが、 y は内側のブロックからのみアクセス出来ます。

fn main() { let x: i32 = 17; { let y: i32 = 3; println!("The value of x is {} and value of y is {}", x, y); } // println!("The value of x is {} and value of y is {}", x, y); // This won't work println!("The value of x is {} and value of y is {}", x, y); // これは動きません }
fn main() {
    let x: i32 = 17;
    {
        let y: i32 = 3;
        println!("The value of x is {} and value of y is {}", x, y);
    }
    println!("The value of x is {} and value of y is {}", x, y); // これは動きません
}

最初の println! は「The value of x is 17 and the value of y is 3」(訳注: 「xの値は17でyの値は3」)と印字する筈ですが、 2つめの println!y がもうスコープにいないため y にアクセス出来ないのでこの例はコンパイル出来ません。 代わりに以下のようなエラーが出ます。

$ cargo build
   Compiling hello v0.1.0 (file:///home/you/projects/hello_world)
main.rs:7:62: 7:63 error: unresolved name `y`. Did you mean `x`? [E0425]
main.rs:7     println!("The value of x is {} and value of y is {}", x, y); // これは動きません
                                                                       ^
note: in expansion of format_args!
<std macros>:2:25: 2:56 note: expansion site
<std macros>:1:1: 2:62 note: in expansion of print!
<std macros>:3:1: 3:54 note: expansion site
<std macros>:1:1: 3:58 note: in expansion of println!
main.rs:7:5: 7:65 note: expansion site
main.rs:7:62: 7:63 help: run `rustc --explain E0425` to see a detailed explanation
error: aborting due to previous error
Could not compile `hello`.

To learn more, run the command again with --verbose.

さらに加えて、変数束縛は覆い隠すことが出来ます(訳注: このことをシャドーイングと言います)。 つまり後に出てくる同じ名前の変数束縛があるとそれがスコープに入り、以前の束縛を上書きするのです。

fn main() { let x: i32 = 8; { // println!("{}", x); // Prints "8" println!("{}", x); // "8"を印字する let x = 12; // println!("{}", x); // Prints "12" println!("{}", x); // "12"を印字する } // println!("{}", x); // Prints "8" println!("{}", x); // "8"を印字する let x = 42; // println!("{}", x); // Prints "42" println!("{}", x); // "42"を印字する }
let x: i32 = 8;
{
    println!("{}", x); // "8"を印字する
    let x = 12;
    println!("{}", x); // "12"を印字する
}
println!("{}", x); // "8"を印字する
let x =  42;
println!("{}", x); // "42"を印字する

シャドーイングとミュータブルな束縛はコインの表と裏のように見えるかもしれませんが、それぞれ独立な概念であり互いに代用が出来ないケースがあります。 その1つにシャドーイングは同じ名前に違う型の値を再束縛することが出来ます。

fn main() { let mut x: i32 = 1; x = 7; // let x = x; // x is now immutable and is bound to 7 let x = x; // xはイミュータブルになって7に束縛されました let y = 4; // let y = "I can also be bound to text!"; // y is now of a different type let y = "I can also be bound to text!"; // yは違う型になりました }
let mut x: i32 = 1;
x = 7;
let x = x; // xはイミュータブルになって7に束縛されました

let y = 4;
let y = "I can also be bound to text!"; // yは違う型になりました