03. 変数・型・関数・制御フロー
🎯 この章のゴール
- 不変・可変の変数とシャドーイングを使い分ける
- 基本的なデータ型を知る
- 「式」と「文」の違いを理解する
💡 ひとことで言うと
Rust は「書き換えるつもりなら宣言しておいてね」という言語。そして ほとんど全部が式(値を返す) です。
変数: 不変がデフォルト
fn main() {
let x = 5;
x = 6; // error[E0384]: cannot assign twice to immutable variable `x`
}fn main() {
let mut x = 5; // mut を付ければ OK
x = 6;
println!("{x}");
const MAX_POINTS: u32 = 100_000; // 定数は型注釈必須、常に不変
println!("{MAX_POINTS}");
}シャドーイング
同じ名前で let し直すと、新しい変数 が前の変数を覆い隠します。型を変えてもOK。
fn main() {
let spaces = " ";
let spaces = spaces.len(); // &str → usize に変身
println!("{spaces}");
}基本のデータ型
| 分類 | 型 | 例 |
|---|---|---|
| 整数 | i8〜i128, u8〜u128, isize, usize | 42, -7, 0xff, 1_000 |
| 浮動小数点 | f32, f64(既定) | 3.14 |
| 真偽値 | bool | true, false |
| 文字 | char(Unicode 1 文字, 4 バイト) | '🦀', 'あ' |
| タプル | (i32, f64, char) | (1, 2.0, 'c') |
| 配列 | [i32; 3](固定長) | [1, 2, 3] |
fn main() {
let tup = (500, 6.4, 'z');
let (x, y, z) = tup; // 分解
let first = tup.0; // インデックスでアクセス
let arr = [3; 5]; // [3, 3, 3, 3, 3]
println!("{x} {y} {z} {first} {}", arr[0]);
}配列の範囲外アクセス(
arr[10])はコンパイル時に見つからなくても、実行時に panic して安全に停止します。メモリを壊したまま走り続けることはありません。
関数と「式」
fn add(a: i32, b: i32) -> i32 {
a + b // ; を付けない = この値を返す
}
fn main() {
let y = {
let x = 3;
x + 1 // ブロックも式。y は 4
};
println!("{} {y}", add(1, 2));
}- 引数には 型注釈が必須
- 最後の式の値が戻り値。
returnは早期リターン用 ;を付けると 文 になり、値は()(ユニット)
制御フロー
fn main() {
let n = 7;
let kind = if n % 2 == 0 { "偶数" } else { "奇数" }; // if も式
let mut count = 0;
let result = loop {
count += 1;
if count == 10 {
break count * 2; // loop は break で値を返せる
}
};
while count > 0 {
count -= 3;
}
for i in (1..=3).rev() { // 3, 2, 1
println!("{i}!");
}
println!("{kind} {result}");
}if の条件は必ず bool。if n { ... } のような書き方(数値を真偽値扱い)はできません。
🧪 やってみよう
下の書きかけコードの TODO を埋めて、▶ 実行 で確かめよう。todo!() のままだと実行時に panic します。
摂氏を華氏に変換する関数を完成させよう(華氏 = 摂氏 × 1.8 + 32)。
fn c_to_f(c: f64) -> f64 {
todo!("華氏 = 摂氏 × 1.8 + 32")
}
fn main() {
for c in [0.0, 10.0, 20.0, 30.0] {
println!("{c}℃ = {}℉", c_to_f(c));
}
}💡 解答例を見る
fn c_to_f(c: f64) -> f64 {
c * 1.8 + 32.0
}
fn main() {
for c in [0.0, 10.0, 20.0, 30.0] {
println!("{c}℃ = {}℉", c_to_f(c));
}
}📝 まとめ
- 変数は不変がデフォルト。
mutで可変、シャドーイングで再定義 ;の無い最後の式がブロックや関数の値になるif/loop/matchは値を返せる式
✅ 理解度クイズ
Q1
次のコードはコンパイルできる?
fn main() {
let x = 5;
x = 6;
println!("{x}");
}
変数はデフォルトで不変です。再代入したいなら let mut x = 5; と宣言します。
Q2
次の関数の戻り値は?
fn f() -> i32 {
let a = 3;
a * 2
}
最後の a * 2 には ; がないので 式 として評価され、その値 6 が戻り値になります。a * 2; と書くと文になり、型が合わずコンパイルエラーになります。
Q3
シャドーイング(
let で同名変数を再定義)と mut の違いとして正しいものは?シャドーイングは新しい変数を作るので let s = "abc"; let s = s.len(); のように型を変えられます。mut は同じ変数を書き換えるので型は固定です。
Q4
let n = if cond { 1 } else { "one" }; はコンパイルできる?if は式なので値を返せますが、すべての分岐が 同じ型 を返す必要があります。i32 と &str は混ぜられません。