はじめに
変数と不変性へようこそ。この実験は、Rust Bookの一部です。LabExでRustのスキルを練習することができます。
この実験では、Rustにおける不変性の概念を探り、変数がデフォルトでは不変である方法と、mut
キーワードを使用して変数を変更可能にする方法について説明します。また、不変性が安全性と並行性にとって重要であることを強調しながら、特定の状況における可変性の有用性も認識します。
This tutorial is from open-source community. Access the source code
💡 このチュートリアルは英語版からAIによって翻訳されています。原文を確認するには、 ここをクリックしてください
変数と不変性へようこそ。この実験は、Rust Bookの一部です。LabExでRustのスキルを練習することができます。
この実験では、Rustにおける不変性の概念を探り、変数がデフォルトでは不変である方法と、mut
キーワードを使用して変数を変更可能にする方法について説明します。また、不変性が安全性と並行性にとって重要であることを強調しながら、特定の状況における可変性の有用性も認識します。
「変数を使った値の格納」で述べたように、デフォルトでは変数は不変です。これは、Rustが提供する安全性と並行性を活かしたコードを書くように促す多くの方法の1つです。ただし、変数を可変にするオプションもあります。Rustが不変性を推奨する方法と理由、そして時々それを避けたい理由を探ってみましょう。
変数が不変の場合、値が名前にバインドされると、その値を変更することはできません。これを示すために、cargo new variables
を使ってproject
ディレクトリに新しいプロジェクト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`
| help: consider making this binding mutable: `mut x`
3 | println!("The value of x is: {x}");
4 | x = 6;
| ^^^^^ cannot assign twice to immutable variable
この例は、コンパイラがプログラムのエラーを見つけるのにどのように役立つかを示しています。コンパイラのエラーは悔やましいかもしれませんが、実際にはあなたのプログラムが安全に望むことを行っていないことを意味するだけで、あなたが良いプログラマでないことを意味するものではありません!経験豊富なRustプログラマもまだコンパイラのエラーを起こします。
x
という不変変数に2回目の値を代入しようとしたため、不変変数
x`に2回代入できません`というエラーメッセージを受け取りました。
不変と指定された値を変更しようとするときにコンパイル時のエラーが発生することは重要です。なぜなら、このような状況はバグにつながる可能性があるからです。コードの一部が値が決して変わらないという前提で動作し、コードの別の部分がその値を変更する場合、コードの最初の部分が意図通りに動作しなくなる可能性があります。この種のバグの原因は、事後に特定するのが難しい場合があります。特に、2番目のコードが値を変更するのはたまにのみ
の場合です。Rustコンパイラは、値が変更されないと宣言した場合、本当に変更されないことを保証するので、自分で追跡する必要はありません。したがって、コードを理解しやすくなります。
しかし、可変性は非常に便利で、コードを書きやすくすることができます。変数はデフォルトで不変ですが、第2章でやったように、変数名の前にmut
を追加することで可変にすることができます。mut
を追加することで、コードの他の部分がこの変数の値を変更することを示すことで、コードの将来の読者に意図を伝えることもできます。
たとえば、src/main.rs
を次のように変更しましょう。
ファイル名: 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
Running `target/debug/variables`
The value of x is: 5
The value of x is: 6
mut
を使用すると、x
にバインドされた値を5
から6
に変更することができます。最終的に、可変性を使用するかどうかはあなた次第であり、その特定の状況で何が最も明確かに依存します。
不変な変数と同様に、定数も名前にバインドされた値であり、変更することはできませんが、定数と変数にはいくつかの違いがあります。
まず、定数にはmut
を使用することができません。定数はデフォルトで不変であるだけでなく、常に不変です。定数はlet
キーワードの代わりにconst
キーワードを使用して宣言し、値の型は必ず指定する必要があります。「データ型」で型と型指定について説明しますので、今は詳細は心配しないでください。ただ、型を常に指定する必要があることだけを知っておいてください。
定数は、グローバルスコープを含む任意のスコープで宣言することができ、これはコードの多くの部分が知る必要がある値に役立ちます。
最後の違いは、定数は実行時にのみ計算できる値の結果ではなく、定数式にのみ設定できることです。
以下は定数宣言の例です。
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
定数の名前はTHREE_HOURS_IN_SECONDS
で、その値は60(1分間の秒数)に60(1時間の分数)に3(このプログラムで数える時間数)を掛けた結果に設定されています。Rustの定数の命名規則は、単語の間にアンダースコアを付けてすべて大文字にすることです。コンパイラはコンパイル時に限定された一連の演算を評価することができるため、この値を10,800
という値に設定する代わりに、理解しやすく検証しやすい方法でこの値を書き出すことができます。定数を宣言する際に使用できる演算に関する詳細は、https://doc.rust-lang.org/reference/const_eval.htmlのRustリファレンスの定数評価のセクションを参照してください。
定数は、宣言されたスコープ内で、プログラムが実行されている間全体で有効です。この特性により、定数は、ゲームのプレイヤーが獲得できる最大ポイント数や光の速度など、プログラムの複数の部分が知る必要があるアプリケーションドメインの値に役立ちます。
プログラム全体で使用されるハードコードされた値を定数として命名することは、その値の意味をコードの将来の保守担当者に伝えるのに役立ちます。また、将来ハードコードされた値を更新する必要がある場合に、コードの変更箇所が1か所だけで済むようにするのにも役立ちます。
第2章の予想ゲームのチュートリアルで見たように、以前の変数と同じ名前で新しい変数を宣言することができます。Rustプログラマーは、最初の変数が2番目の変数によって「シャドーされる」と言います。これは、変数の名前を使用するときに、コンパイラが見るのは2番目の変数であることを意味します。実際のところ、2番目の変数が最初の変数を上書きし、変数名のすべての使用を自身に引き付けます。それ自体がシャドーされるか、スコープが終了するまでです。次のように、同じ変数名を使用してlet
キーワードを繰り返すことで、変数をシャドーすることができます。
ファイル名: src/main.rs
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
の値にバインドします。次に、let x =
を繰り返すことで新しい変数x
を作成し、元の値に1
を加えます。その結果、x
の値は6
になります。次に、波括弧で作成された内側のスコープ内で、3番目のlet
文も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
The value of x is: 6
シャドーイングは、変数をmut
としてマークすることとは異なります。なぜなら、let
キーワードを使わずに変数に再代入しようとすると、コンパイル時エラーが発生するからです。let
を使うことで、値にいくつかの変換を行うことができますが、変換が完了した後は変数を不変にすることができます。
mut
とシャドーイングのもう1つの違いは、let
キーワードを再度使うときに実際に新しい変数を作成するため、値の型を変更することができる一方で、同じ名前を再利用できることです。たとえば、プログラムがユーザーにいくつの空白文字を入力するかを求め、その入力を数値として格納したい場合を考えてみましょう。
let spaces = " ";
let spaces = spaces.len();
最初のspaces
変数は文字列型で、2番目のspaces
変数は数値型です。したがって、シャドーイングにより、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
|
2 | let mut spaces = " ";
| ----- expected due to this value
3 | spaces = spaces.len();
| ^^^^^^^^^^^^ expected `&str`, found `usize`
ここで、変数の仕組みを理解しましたので、変数が持ちうるさらに多くのデータ型を見てみましょう。
おめでとうございます! あなたは「変数と不変性」の実験を完了しました。あなたのスキルを向上させるために、LabExでさらに多くの実験を練習することができます。