【Rust入門】変数と定数を分かりやすく解説

Rustの変数と定数
プログラムを実行していく際には、ある値をコンピューターのメモリ上に格納し、取り出しながら様々な処理を実行していきます。ある値を含む場所のメモリをオブジェクトと呼びます。そして、そのオブジェクトを識別するための識別子が必要です。
ある値(オブジェクト)を識別子(名前)に束縛することで、変数(variable)を定義します。また、プログラムを実行している最中に値が変更されることのない、あらかじめ決められた値は定数(constant)と呼ばれます。
この記事では、Rustにおける変数と定数の扱いや使い方について解説します。
Rustの変数は基本イミュータブルで変更不可
Rustで最初に覚えておいてほしいことは「変数は基本イミュータブルで変更ができない」ということです。他のプログラミング言語を学んできた人は変数の値を変えられることが普通であることが多かったと思いますし、変数という名前から考えると違和感を覚えるかもしれません。
- ミュータブル:変更可能で、作成後も内容を変更することができます。
- イミュータブル:変更不可能で、一度作られたらその内容を変更することはできません。
例えば、Haskellなどの関数型プログラミング言語は、変数がイミュータブルで、関数も副作用を持たないように設計されているため、参照透過性が高く、同じ入力には常に同じ出力が保証されます。変数の値が変更できないことは、不便に思うかもしれないですが、一方で値が変わらないことが保証されるため、バグがないプログラムを書くことが可能になります。
Rustでの変数定義と利用の例を見てみましょう。
fn main() { let x = 5; println!("xの値: {}", x); // 以下はエラーとなる x = 6; }
【コンパイル結果】 error[E0384]: cannot assign twice to immutable variable `x` --> examples\immutable.rs:7:5 | 2 | let x = 5; | - first assignment to `x` ... 7 | x = 6; | ^^^^^ cannot assign twice to immutable variable | help: consider making this binding mutable | 2 | let mut x = 5; | +++
Rustでは、変数の定義に let
キーワードを使い、変数 x
に =
で値を設定します。「let x = 5;
」のような記載を「文」といい、Rustでは、値を束縛するという表現をします。また、変数はスネークケース(snake_case
)という小文字単語をアンダースコアでつなぐ形式にするのが通例です。
上記例では、一度定義した x に対して、x = 6;
という形で値を変更しようとしています。このような場合には、コンパイルの際に「cannot assign twice to immutable variable
」というようなエラーが発生します。これは、イミュータブルな変数に2度、値を設定しようとしているというエラーです。
ただし、コンパイル結果の help:
の記載を見てみると分かりますが、Rustでは値を変更するための方法があります。以降ではその方法を見てみましょう。
変更するにはミュータブル mut
の指定が必要
Rustで値を変更する変数の場合には、let mut
という形で mut
キーワードを指定します。
fn main() { // ミュータブルな変数として定義する let mut x = 5; println!("xの値: {}", x); // ミュータブルな変数に値を再度設定する x = 6; println!("xの値: {}", x); }
【実行結果】 xの値: 5 xの値: 6
上記の結果を見てみると分かるように、今度は x
の値を変更してもエラーは発生せずに実行を完了することができます。
シャドーイング
Rustでは、一度定義した変数を再度 let
を用いて再定義することができます。このことを「シャドーイング(shadowing)」と言います。
シャドーイングの基本
以下の例では、一度 let x = 5;
で定義した変数 x について、再度 let x = 10;
で再定義しています。
fn main () { let x = 5; println!("xの値: {}", x); // xをシャドーイングする let x = 10; println!("xの値: {}", x); }
【実行結果】 xの値: 5 xの値: 10
上記で見たように一度定義した x
の値を変更することは、イミュータブルな性質から不可能でした。しかし、今回の例での let
を使った再定義は問題なくできます。この時に、もともと定義していた x = 5;
を後の定義が覆い隠してしまうことから、シャドーイング(shadowing)と呼ばれるわけです。
同じスコープ内では、前に定義した x = 5;
の値は二度と使えなくなるため、スコープを抜けたタイミングで 5
が格納されているメモリ領域は自動で解放されます。
スコープとは、変数の有効範囲を定めるコードブロック({}
)の範囲のことを言います。Rustでは、変数はスコープ内で有効であり、スコープを抜けると所有権が失われ、自動的にリソース(メモリ)が解放されます。C/C++のように free
などで手動でメモリを開放する必要がなく、Rustではこの処理がコンパイラと言語仕様で保証されているため、メモリ解放忘れのバグを防止できます。所有権については、少し難しいのでまた別途取り上げます。
異なるスコープでのシャドーイング
異なるスコープでシャドーイングによる変数の再定義をした場合に、どのような挙動になるのかを以下の例で確認してみましょう。
fn main () { let x = 5; println!("xの値: {}", x); { // 異なるスコープでシャドーイングする let x = 10; println!("xの値: {}", x); } println!("xの値: {}", x); }
【実行結果】 xの値: 5 xの値: 10 xの値: 5
上記例では、let x = 5;
で一度定義した変数を一段深いスコープで let x = 10;
で再定義しています。このとき、このスコープ内で x
の値は 10
ですが、スコープを抜けると外側の x = 5;
に戻ります。そのため、println!
で 5
が表示されていることが分かります。なお、10
が格納されているメモリは、スコープを抜けた際に解放されます。このように異なるスコープ間でのシャドーイングの挙動はよく理解しておきましょう。
const
定数
Rustで定数を定義するためには、const
キーワードを使用して定義します。
定数とはコンパイル時に値が確定しているもので、一度決めたら絶対に変わらない値を定義する際に使用します。例えば、システムで共通に使用するパラメータを定義するような場合です。
定数は、大文字のスネークケース(SNAKE_CASE
)で定義するのが通例です。定数は関数内でも定義できますが、実際にはプログラム全体で使う値を定義するために、グローバルに定義されることが多いです。
定数は以下のように定義して使用します。
// 定数を定義 (型注釈は必須) const MAX_USERS: u32 = 100; fn main () { // 定数を利用 println!("MAX_USERS: {}", MAX_USERS) }
【実行結果】 MAX_USERS: 100
変数と定数の違いの一つとして、型定義が必須であることがあげられます。変数は型推論がされるため、指定は必須ではありません。上記例では、MAX_USERS: u32 = 100;
の : u32
という部分が 32ビットの符号なし整数であることを示しています。Rustの標準的な型については、別途取り上げて説明しようと思います。
また、コンパイル時に決定する性質であることから、動的に確保されるヒープ領域に格納される型(Vec<T>
など)での定義もできません。
まとめ
この記事では、Rustにおける変数と定数の基本的な使い方について解説しました。
Rustの変数はデフォルトでイミュータブル(変更不可)であり、安全性を高めるための設計となっています。一方で、mut
キーワードを使うことで明示的に可変な変数も定義できるため、安全性と柔軟性のバランスがとられています。
同じ名前の変数を再定義する「シャドーイング」やスコープにより変数の有効範囲が制限される仕組みもRustの所有権やメモリ管理の考え方と密接に関係しています。また、const
によってコンパイル時に固定される定数を定義することで、値の意味付けや再利用性を高めることができます。
Rustを学び始めたばかりの段階では、このような「変数と定数の基本的なルール」をしっかり理解しておくと、Rustの中心的な概念である所有権やライフタイムなどについても理解しやすくなります。ぜひ基本をしっかりと押さえておいてください。