すでに第2章の「変数に値を格納する」の項で見たように、変数はデフォルトでは不変(immutable)です。これは、Rustが提供する安全性と容易な並行処理(コンカレンシー)という恩恵をあなたのコードが受けられるように、Rustが行なう「お節介(後押し)」のひとつなのです。とはいえ、変数を可変にする選択肢も残されています。では、なぜ、どのように、Rustが変数の不変性をあなたに勧め、なぜ時にあなたがそれに反したくなるのか、を探求しましょう。
さて、「変数が不変である」という意味は、一旦「ある値」が「変数名」に結び付けられると、あなたはその値を変更できないということです。これを説明するために、cargo new variables
を使ってあなたのプロジェクト・ディレクトリに「variables」(変数)という名前のプロジェクトを作成してください。
そして、新たに作成したvariablesディレクトリの中のsrc/main.rsを開き、下記のコードで書き換えてください。このコードではコンパイルされません。不変性エラーの検証をしましょう。
《訳注》
src/main.rs
のような/
で区切られた文字列は、ファイルの保存場所を示しています(この場合は「src」ディレクトリにある「main.rs」プログラム)。実行結果に表示されているtarget/debug/variables
も同じ意味(この場合は、「target」ディレクトリの中の「debug」ディレクトリの中にある「variables」プログラム)。
fn main() {
let x = 5;
println!("The value of x is: {}", x);
x = 6;
println!("The value of x is: {}", x);
}
コードを保存し、このプログラムをcargo run
で実行します。あなたは、以下のようなエラー・メッセージを受け取ることでしょう。
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
※ プログラム「variables」(プログラム・バージョン 0.1.0)コンパイル中。
error[E0384]: cannot assign twice to immutable variable `x`
※ エラー発生: 不変変数`x`に二度値を割り当てできません。
--> src/main.rs:4:5
※ プログラム位置 src/main.rs 第4行、5文字目
|
2 | let x = 5;
| -
| |
| first assignment to `x`
※ 不変変数`x`への初回値「5」の割り当て
| help: consider making this binding mutable: `mut x`
※ 修正提案: この不便変数を可変変数にする: `mut x`へ変更
3 | println!("The value of x is: {}", x);
4 | x = 6;
| ^^^^^ cannot assign twice to immutable variable
※ 不変変数に二度値を割り当てられません。
For more information about this error, try `rustc --explain E0384`.
※ このエラーに関するより詳細な情報は、`rustc --explain E0384`で検索してください。
error: could not compile `variables` due to previous error
※ エラー発生: 上記エラーのため、プログラム「variables」をコンパイルできません。
このように、コンパイラはあなたのプログラム中のエラーを見つける手助けをしてくれます。コンパイル・エラーは癪に障るものですが、これはあなたのプログラムが駄目と言っているのではなく、実はあなたのプログラムを実行するのは安全ではないことを示しているだけなのです。経験を積んだRustプログラマでさえ、コンパンラー・エラーを受け取っています。
このエラーメッセージ例では、エラーの原因はあなたがcannot assign twice to immutable variable 'x'
、即ち「不変変数 x には、値を2回結び付けできない」ことであることを示しています(コードでは不変変数 x に二つ目の値を結びつけようとしています)。
重要なのは、「不変指定された変数」の値を変更しようとした場合、プログラムのバグになり易いため、このコンパイル時エラーを受け取るということです。たとえば、プログラムのある部分でその変数が不変である前提で動作しているときに、別の部分でその変数が変更された場合、変数が不変のプログラム部分は、設計通りの動作をしないことになります。この種類のバグは、特に変数変更部分の値の変更がまれにしか発生しない場合、原因の特定が困難になりがちです。Rustのコンパイラは、あなたがこのような問題に煩わされないように、「不変」と宣言した値は絶対に変更されないことを保証しているのです。あなたのコードは、こうして初めから終わりまで論理的に通しがよくなります。
しかし「可変性 mutability」も非常に有用で、コードの作成が一層便利になります。変数はデフォルトでのみ不変でした。第2章のプログラム例題で行なったように、変数名の前にmut
を付け加えることで変数を可変に出来ます。mut
を付け加えることはまた、このプログラムの別の場所では変数の値が変更されることを示し、将来あなたのコードを読む人に対してその意図を明確にしています。
では、先のsrc/main.rsを次のように書き換えてください。
fn main() {
let mut x = 5;
println!("The value of x is: {}", x);
x = 6;
println!("The value of x is: {}", x);
}
このプログラムを実行すると、今回は次のように表示されます.
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
Finished dev [unoptimized + debuginfo] target(s) in 0.30s
※ 開発モードで目的プログラムのコンパイル完了、所要時間 0.30秒
Running `target/debug/variables`
※ プログラム「variables」を実行中
The value of x is: 5 ※ xの値は: 5
The value of x is: 6 ※ xの値は: 6
修飾子mut
を用いることで、変数x
に結び付けられた値が5
から6
に変更されました。
このmut
を用いる方法には、バグの防止の他に、いくつかの考慮すべきトレードオフ(損得)があります。たとえば、大規模なデータ構造体を使用している場合、インスタンス(対象データ)をその場で直接(インプレースで)変更するほうが、新しく割り当てられたインスタンスをコピーして返すよりも処理が高速になる場合があります。データ構造が小さい場合は、新しいインスタンスを生成し、関数型プログラミングスタイルで記述するほうが処理が判りやすくなるため、パフォーマンスの低下はその明解さを得るためのペナルティ(代償)と考えることもできます。
不変変数同様に、定数 constants は名前(定数名)に結びつけられた値で、変化することが許されていませんが、「定数」と「変数(不変変数)」ではいくらか違いがあります。
まず第一に、「定数」に対しては修飾子 mut
を用いることができません。定数は単にデフォルトとして不変なのではなく、いつ、どのようなときも不変なのです。定数を宣言する場合には、let
キーワードではなく、const
キーワードを使用し、その値の型(type)を必ず明示しなければなりません。「型」と「型の明示」については次節『3.2. データの型』で取り上げますのでご心配なく。現時点では、常に型を明示しなければならないことだけを覚えておいてください。
次に、定数は、グローバルスコープを含む任意のスコープ(scope、有効範囲)の中で宣言できるため、「コードの多くの部分で参照する必要のある値」を宣言するのに役立ちます。
そして最後の違いは、定数は 「定数式」(constant expression)のみを設定できる ということで、プログラムの実行時に計算に用いられるだけの値(計算された結果)を設定しないということです。
以下は、定数の宣言例です。
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
この事例の定数名は THREE_HOURS_IN_SECONDS
(「3時間の秒表示」)で、その値は「60(1分間中の秒数)と60(一時間中の分数)と3(このプログラム例で計測したい時間数)を掛け合わせた結果」に設定されています(Rustの「定数」命名規則は、全て大文字表記し各語をアンダースコア記号「 _ 」で繋ぐことになっています)。このようにすることで、コンパイラはコンパイル時に所定の演算結果を評価でき、なおかつ、単にこの定数を数値で「10,800」とだけ設定するよりも、私たち自身にもその内容を「理解しやすく検証可能な形で」この定数を表記するように仕向けているのです。
定数宣言時にどのような演算が用いられるかについての詳しい説明は『Rustリファレンス(Rust Reference)』の「第17節 定数の評価(Constant evaluation)」を参照してください。
定数はプログラムの実行はいつでも、宣言されたスコープ(有効範囲=一対の{ }で囲まれた範囲)の中で、有効です。この特性は、あなたのプログラムの複数個所の部分で必要とされる値、たとえば、ゲームのプレーヤーが獲得できる「最大ポイント」や「光の速度」といった値では定数指定が便利です。
プログラム内の「ハードコード値」(決め打ちされた値)を定数として指定することは、将来そのプログラムのメンテナンスを行なう人にその値の意味を伝達するのに役立ちます。また、将来的にその「ハードコード値」を変更する必要が生じた場合、定数指定であればたったの一箇所だけを変更するだけで済むようになるのです。
第2章の「数当てゲーム」演習で用いたように、新しい変数を既にある変数と同じ名前を用いて宣言することができます。Rustではこれを「最初の変数が新しい同名の変数で覆い隠された(シャドウされた)」といい、その変数名が用いられた時には新しい(覆い隠した)変数の値がプログラムから見えるということになります。同じ変数名を用いて変数をシャドウイングする(異なる値を結び付ける)には、let
キーワードを用います(次例参照)。
fn main() {
let x = 5;
let x = x + 1;
{
let x = x * 2;
println!("The value of x in the inner scope is: {}", x);
}
println!("The value of x is: {}", x);
}
このプログラム例では、最初に変数 x
を値 5
に結び付けます。それから、この変数x
にlet x =
を繰り返すことによりシャドウイングし、最初の値(5
)に1
を加えて値を6
にします。更に、その内側のスコープ(有効範囲)内で、三番目のlet X =
文により、再度変数x
はシャドウイングされて、直前の値を2
倍し、変数X
を値12
にしています。この内側のスコープ(有効範囲)が終わると、内側のシャドウイングも終了し、変数x
は6
の状態に戻ります。このプログラムを実行すると以下の結果が得られます。
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/variables`
The value of x in the inner scope is: 12
※ 内側のスコープでの x の値は: 12
The value of x is: 6
※ x の値は: 6
シャドウイングは「変数を可変にする(mut
)」のではありません。なぜならば、let
キーワードを用いずにこの変数に別の値を再設定しようとすると、コンパイル・エラーが発生します。シャドウイングはlet
を用いることで一時的な値の変更を行ないますが、その一時的な変更のスコープが終わると、この変数の「不変性」は元に戻るのです。
もうひとつmut
とシャドウイングが異なるのは、let
キーワードを再び用いると、値の型が異なる変数を同じ名前で、新しい変数として効率的に作成できることです。たとえば、プログラムがユーザーに対して、文字列の間にいくつスペースが必要かを空白文字を入力して示すことを求め、そのスペースの数をデータとして保存する場合を考えます。
let spaces = " ";
let spaces = spaces.len();
最初のspaces
変数は「ストリング型」(string type)で、2番目のspaces
は「数値型}(number type)です。このようにシャドウイングを用いることで、私たちはspaces_str
とspaces_num
というように、それぞれに異なる変数名を付与する必要がなくなり、代わりにspaces
という簡潔な名前を再利用できるのです。しかしmut
をこの場合に使おうとすると、次のように、コンパイル時にエラーとなります。
let mut spaces = " ";
spaces = spaces.len();
このエラーは、以下の実行結果が示すように「変数の型は'可変化'出来ない」ということです。
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
error[E0308]: mismatched types
※ エラー発生: 型不整合
--> src/main.rs:3:14 ※ 発生個所 3行目14桁目
|
2 | let mut spaces = " ";
| ----- expected due to this value
※ ここの値は「スペース記号」=文字列
3 | spaces = spaces.len();
| ^^^^^^^^^^^^ expected `&str`, found `usize`
※ 「文字列型 &str」が必要、実際は「usize型」を指定
For more information about this error, try `rustc --explain E0308`.
error: could not compile `variables` due to previous error
これで、私たちは変数がどのように働くのかを見てきました。次は、変数が持つ「データの型」について見ていきましょう。