注意: 最新版のドキュメントをご覧ください。この第1版ドキュメントは古くなっており、最新情報が反映されていません。リンク先のドキュメントが現在の Rust の最新のドキュメントです。

変数束縛

事実上全ての「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は違う型になりました