他の言語での開発経験がある方にとって、Rustの学習で最初に直面するカルチャーショックが「変数の扱い」です。
C++やJava、Pythonなどでは「変数はデフォルトで書き換え可能(Mutable)」であることが一般的ですが、Rustでは「デフォルトで不変(Immutable)」です。この設計こそが、Rustがコンパイル時に多くのバグを排除できる理由の根幹にあります。
第2章では、Rustの構文の基礎と、その背後にある哲学を学びます。
Rustでは変数を宣言するために let キーワードを使用します。しかし、単に let で宣言された変数は、値を一度代入すると二度と変更できません。
まず、以下のコードを見てください。これは意図的にコンパイルエラーになるように書かれています。
fn main() {
let x = 5;
println!("xの値は: {}", x);
// 値を再代入しようとする(コンパイルエラーになる)
x = 6;
println!("xの値は: {}", x);
}error[E0384]: cannot assign twice to immutable variable `x` --> immutability_error.rs:6:5 | 2 | let x = 5; | - first assignment to `x` ... 6 | x = 6; | ^^^^^ cannot assign twice to immutable variable |
これをコンパイルしようとすると、Rustコンパイラは「不変変数 x に二度代入することはできない」と強く叱ってくれます。
値を変更したい場合は、mut(mutableの略)キーワードを明示的に付ける必要があります。これにより、「この変数は値が変わる可能性がある」とコードの読み手やコンパイラに宣言します。
fn main() {
// mut をつけることで可変になる
let mut x = 5;
println!("xの値は: {}", x);
x = 6;
println!("xの値は: {}", x);
}xの値は: 5 xの値は: 6
なぜデフォルトが不変なのか? 大規模なシステムや並行処理において、「いつの間にか値が変わっている」ことはバグの主要な原因です。Rustは「変更が必要な箇所だけを明示的にする」ことで、コードの予測可能性と安全性を高めています。
Rustには、他の言語ではあまり見られないシャドーイング(Shadowing)という強力な機能があります。これは、同じ名前の変数を let を使って再宣言することで、前の変数を「覆い隠す」機能です。
mut との違いは2点あります:
mut は値の変更しかできませんが、シャドーイングは新しい変数を宣言しているのと同じなので、型を変えることができます。fn main() {
let x = 5;
// 最初のシャドーイング: 値を加工する
let x = x + 1;
{
// 内側のスコープでのシャドーイング
let x = x * 2;
println!("内側のスコープでのx: {}", x);
}
// スコープを抜けると、外側のxの値が参照される
println!("外側のスコープでのx: {}", x);
// 型の変更を伴うシャドーイングの例
let spaces = " "; // 文字列スライス型
let spaces = spaces.len(); // usize型(整数)
println!("スペースの数: {}", spaces);
}内側のスコープでのx: 12 外側のスコープでのx: 6 スペースの数: 3
他言語では spaces_str、spaces_num のように変数名を変える場面でも、Rustでは変数の意味が変わらなければ同じ名前を再利用してコードをすっきり保つことができます。
Rustは静的型付け言語ですが、型推論が強力なため、多くの場合に型注釈は不要です。しかし、ここでは明示的な理解のために型を見ていきます。
i32 (デフォルト), i64, u32, u8 など。isize, usize (インデックス用によく使われる)。f64 (デフォルト, 倍精度), f32 (単精度)。bool (true / false)。char。Rustのcharは4バイトで、Unicodeスカラー値を扱います(ASCIIだけでなく、漢字や絵文字も1文字として扱えます)。シングルクォート ' で囲みます。Vec)」を使います。配列はスタック上に確保されるため、高速ですがサイズ変更はできません。fn main() {
// --- スカラー型 ---
let int_val: i32 = -10;
let float_val: f64 = 2.5;
let char_val: char = 'あ'; // Unicode文字
let emoji: char = '🦀';
println!("Scalar: {}, {}, {}, {}", int_val, float_val, char_val, emoji);
// --- 複合型: タプル ---
// 型が異なっていてもOK
let tup: (i32, f64, u8) = (500, 6.4, 1);
// 分解(Destructuring)して値を取り出す
let (x, y, z) = tup;
println!("Tuple分解: yの値は {}", y);
// ドット記法でアクセス
println!("Tupleアクセス: 最初の値は {}", tup.0);
// --- 複合型: 配列 ---
// 型と長さを指定: [型; 長さ]
let arr: [i32; 5] = [1, 2, 3, 4, 5];
// 同じ値で初期化する記法 let a = [3; 5] は [3, 3, 3, 3, 3]
let months = ["Jan", "Feb", "Mar"];
println!("Array: 2番目の月は {}", months[1]);
}Scalar: -10, 2.5, あ, 🦀 Tuple分解: yの値は 6.4 Tupleアクセス: 最初の値は 500 Array: 2番目の月は Feb
mut をつけない限り変更できない。これにより安全性が担保される。
let を重ねることで変数の再定義が可能。値の加工や型の変更に便利。他の言語では「定数」として扱われるような使い方が、Rustでは「デフォルトの変数」になります。最初は窮屈に感じるかもしれませんが、「変化するものは目立つようにする」というRustの哲学に慣れると、コードの流れが追いやすくなることに気づくはずです。
以下の手順に従ってコードを書いてください。
x を定義し、文字列 "100" を代入する。x を数値の 100 (文字列からパースする)に変換する。
"100".parse().expect("Not a number") で数値に変換できます。x に 50 を足した値にする。x の値を表示する。data を定義し、[1.1, 2.2, 3.3] で初期化する。result を定義し、配列 data の最初の要素と最後の要素を格納する。mut が必要ない理由を考える。