変数と可変性
「変数による値の格納」節で触れたとおり、デフォルトでは、 変数は不変です。これは、Rust が提供する安全性と容易な並行性を活かす 形でコードを書くように Rust が促す、数多くの後押しの1つです。しかし、 それでも変数を可変にする選択肢はあります。Rust がどのように、そして なぜ不変性を優先するよう促すのか、また、ときにはなぜその方針から 外れたくなるのかを見ていきましょう。
変数が不変である場合、ひとたび値が名前に束縛されると、その値を変更
することはできません。これを示すために、cargo new variables を
使って、projects ディレクトリ内に variables という新しい
プロジェクトを作成してください。
次に、新しい variables ディレクトリで src/main.rs を開き、その コードを次のコードに置き換えてください。この時点ではまだコンパイル できません。
ファイル名: src/main.rs
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)
error[E0384]: cannot assign twice to immutable variable `x`
--> src/main.rs:4:5
|
2 | let x = 5;
| - first assignment to `x`
3 | println!("The value of x is: {x}");
4 | x = 6;
| ^^^^^ cannot assign twice to immutable variable
|
help: consider making this binding mutable
|
2 | let mut x = 5;
| +++
For more information about this error, try `rustc --explain E0384`.
error: could not compile `variables` (bin "variables") due to 1 previous error
この例は、コンパイラがプログラム内のエラーを見つけるのをどのように 助けてくれるかを示しています。コンパイラエラーは苛立たしいものかも しれませんが、実際には、あなたのプログラムがまだ安全に望んだことを していないというだけの意味です。決して、あなたが優れた プログラマではないという意味ではありません! 経験豊富な Rustacean でも、コンパイラエラーは出ます。
cannot assign twice to immutable variable `x` という
エラーメッセージを受け取ったのは、不変の x 変数に2つ目の値を
代入しようとしたからです。
不変として指定された値を変更しようとしたときにコンパイル時エラーが
出ることは重要です。まさにこのような状況がバグにつながる可能性が
あるからです。コードのある部分が、値は決して変わらないという前提で
動作している一方で、別の部分がその値を変更してしまうと、最初の部分の
コードが設計どおりに動かなくなる可能性があります。この種のバグの原因は、
事後に追跡するのが難しいことがあります。特に、2つ目のコード片がその値を
changed only sometimes? Wait translation error.
このプログラムでは、まず x を 5 という値に束縛します。次に、let x = を
繰り返すことで新しい変数 x を作成し、元の値に 1 を加えるため、
x の値は 6 になります。さらに、中かっこで作られた内側のスコープ内では、
3つ目の let 文も x をシャドーイングして新しい変数を作成し、1つ前の値を
2 倍して、x の値を 12 にします。そのスコープが終わると、内側の
シャドーイングも終了し、x は再び 6 に戻ります。
このプログラムを実行すると、次のように出力されます。
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/variables`
The value of x in the inner scope is: 12
The value of x is: 6
シャドーイングは、変数を mut としてマークすることとは異なります。というのも、
let キーワードを使わずにこの変数へ誤って再代入しようとすると、コンパイル時
エラーになるからです。let を使うことで、値に対していくつかの変換を行いつつ、
それらの変換が完了したあとは変数を不変のままにできます。
mut とシャドーイングのもう1つの違いは、再び let キーワードを使うと
実質的に新しい変数を作成しているため、同じ名前を再利用しながら値の型を
変更できることです。たとえば、あるプログラムが、テキストの間にいくつ空白を
入れたいかをスペース文字の入力でユーザーに示してもらい、その入力をその後
数値として格納したいとします。
fn main() {
let spaces = " ";
let spaces = spaces.len();
}
最初の spaces 変数は文字列型で、2つ目の spaces 変数は数値型です。
そのため、シャドーイングを使うと、spaces_str や spaces_num のような
別の名前を考え出さずに済み、その代わりによりシンプルな spaces という名前を
再利用できます。しかし、ここに示すように、このために mut を使おうとすると、
コンパイル時エラーになります。
fn main() {
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
|
2 | let mut spaces = " ";
| ----- expected due to this value
3 | spaces = spaces.len();
| ^^^^^^^^^^^^ expected `&str`, found `usize`
For more information about this error, try `rustc --explain E0308`.
error: could not compile `variables` (bin "variables") due to 1 previous error
変数がどのように機能するかを見てきたので、次は変数が取り得る、より多くの データ型を見ていきましょう。