第3章: 基本構文と変数

学習目標

  • Rustの基本的な構文を理解する
  • 変数の宣言と可変性の概念を学ぶ
  • Rustの型システムの基礎を把握する
  • シャドーイングの仕組みを理解する

---

3.1 変数と可変性

3.1.1 不変変数(Immutable Variables)

Rustでは、デフォルトで変数は不変(immutable)です:

fn main() {
    let x = 5;
    println!("x の値: {}", x);

    // エラー!xは不変
    x = 6; // ❌ cannot assign twice to immutable variable
}

なぜデフォルトで不変?

┌─────────────────────────────────────────────────────────┐
│            不変変数のメリット                             │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  1. 安全性                                               │
│     └─ 予期しない変更を防ぐ                              │
│                                                         │
│  2. 並行処理                                             │
│     └─ データ競合の心配なし                              │
│                                                         │
│  3. 最適化                                               │
│     └─ コンパイラが積極的に最適化可能                    │
│                                                         │
│  4. 可読性                                               │
│     └─ 値が変わらないことが保証される                    │
│                                                         │
└─────────────────────────────────────────────────────────┘

3.1.2 可変変数(Mutable Variables)

可変にしたい場合は、明示的に mut キーワードを使用:

fn main() {
    let mut x = 5;
    println!("x の値: {}", x);

    x = 6; // ✅ OK
    println!("x の値: {}", x);
}

mutの意図

let mut counter = 0;  // 「このカウンターは変更される」と明示
counter += 1;         // 意図的な変更

let config = load_config();  // 「設定は読み取り専用」と暗示
// config.value = 10; // ❌ エラー:変更は想定外

3.1.3 定数(Constants)

定数は常に不変で、型注釈が必須:

// グローバルスコープで定義可能
const MAX_POINTS: u32 = 100_000;
const PI: f64 = 3.14159;

fn main() {
    println!("最大ポイント: {}", MAX_POINTS);
}

定数 vs 不変変数

特性 定数(const) 不変変数(let)
スコープ グローバル可 関数内のみ
型注釈 **必須** 省略可(型推論)
実行時計算 **不可** 可能
命名規則 SCREAMING_SNAKE_CASE snake_case

const HOUR: u32 = 60 * 60; // ✅ コンパイル時定数

let hour = get_current_hour(); // ✅ 実行時に計算
// const HOUR2: u32 = get_current_hour(); // ❌ エラー

3.1.4 シャドーイング(Shadowing)

同じ名前の変数を再宣言できる:

fn main() {
    let x = 5;
    let x = x + 1;      // 新しいxがシャドーイング
    let x = x * 2;      // さらにシャドーイング

    println!("x の値: {}", x); // 12
}

シャドーイングの利点

  • 型を変換できる
let spaces = "   ";       // &str型
let spaces = spaces.len(); // usize型に変換

  • スコープを制御できる
fn main() {
    let x = 5;

    {
        let x = x * 2; // 内側のスコープでシャドーイング
        println!("内側: {}", x); // 10
    }

    println!("外側: {}", x); // 5(元のxが有効)
}

シャドーイング vs mut

// ❌ mutでは型を変えられない
let mut guess = "42";
guess = guess.parse::<i32>().unwrap(); // エラー!

// ✅ シャドーイングなら可能
let guess = "42";
let guess = guess.parse::<i32>().unwrap(); // OK

---

3.2 データ型

3.2.1 スカラー型(Scalar Types)

Rustには4つの基本スカラー型があります:

1. 整数型(Integer Types)

サイズ 符号付き 符号なし
8-bit i8 u8
16-bit i16 u16
32-bit i32 u32
64-bit i64 u64
128-bit i128 u128
arch isize usize

値の範囲

符号付き (i32):   -2,147,483,648 ~ 2,147,483,647
符号なし (u32):    0 ~ 4,294,967,295

整数リテラル

let decimal = 98_222;        // 10進数(読みやすく_を使用可)
let hex = 0xff;              // 16進数
let octal = 0o77;            // 8進数
let binary = 0b1111_0000;    // 2進数
let byte = b'A';             // バイト(u8のみ)

型サフィックス

let x = 42i32;    // i32型
let y = 100u8;    // u8型

オーバーフローの扱い

// デバッグビルド:panic!
// リリースビルド:ラップアラウンド(環状加算)
let x: u8 = 255;
let y = x + 1; // リリースでは0になる

// 明示的な処理
let x: u8 = 255;
let y = x.wrapping_add(1);    // 0(ラップアラウンド)
let z = x.checked_add(1);     // None(オーバーフロー検出)
let w = x.saturating_add(1);  // 255(飽和演算)

2. 浮動小数点型(Floating-Point Types)

let x = 2.0;      // f64(デフォルト)
let y: f32 = 3.0; // f32(明示的)

  • f32: 単精度(32ビット)
  • f64: 倍精度(64ビット、推奨)

注意点

// ❌ 浮動小数点の等価比較は危険
let x = 0.1 + 0.2;
if x == 0.3 {  // falseになる可能性あり!
    // ...
}

// ✅ イプシロン比較
let epsilon = 1e-10;
if (x - 0.3).abs() < epsilon {
    // 許容範囲内で等しい
}

3. ブール型(Boolean Type)

let t = true;
let f: bool = false;

// よく使用される場面
if t {
    println!("真です");
}

let is_valid = x > 0 && y < 100;

4. 文字型(Character Type)

let c = 'z';
let z: char = 'ℤ';
let heart_eyed_cat = '😻';

// charは4バイト(Unicode Scalar Value)
// 範囲:U+0000 ~ U+D7FF、U+E000 ~ U+10FFFF

charとStringの違い

let c: char = 'A';         // シングルクォート、1文字
let s: &str = "Hello";     // ダブルクォート、文字列

3.2.2 複合型(Compound Types)

1. タプル(Tuple)

異なる型をまとめる:

let tup: (i32, f64, u8) = (500, 6.4, 1);

// 分解(destructuring)
let (x, y, z) = tup;
println!("y の値: {}", y);

// インデックスアクセス
let five_hundred = tup.0;
let six_point_four = tup.1;
let one = tup.2;

空のタプル(unit)

let unit = ();  // 値を返さない関数の戻り値型

2. 配列(Array)

固定長同じ型の要素:

let a = [1, 2, 3, 4, 5];
let months = ["January", "February", "March", /* ... */];

// 型注釈
let a: [i32; 5] = [1, 2, 3, 4, 5];
//      ^^^ ^^^
//      型  長さ

// 同じ値で初期化
let a = [3; 5]; // [3, 3, 3, 3, 3]
//       ^ ^
//       値 個数

アクセス

let first = a[0];
let second = a[1];

// ❌ 境界外アクセス
let index = 10;
let element = a[index]; // panic! at runtime

配列 vs ベクタ

特性 配列(Array) ベクタ(Vec)
サイズ 固定 可変
メモリ スタック ヒープ
性能 高速 若干遅い
使用場面 サイズ既知 動的データ

let array = [1, 2, 3];           // スタック割り当て
let vector = vec![1, 2, 3];      // ヒープ割り当て

---

3.3 関数

3.3.1 関数の定義

fn main() {
    println!("Hello, world!");
    another_function();
}

fn another_function() {
    println!("別の関数です");
}

命名規則snake_case(スネークケース)

3.3.2 引数(Parameters)

fn print_value(x: i32) {
    println!("x の値: {}", x);
}

fn add(a: i32, b: i32) {
    println!("合計: {}", a + b);
}

fn main() {
    print_value(5);
    add(3, 7);
}

型注釈は必須

// ❌ エラー
fn add(a, b) {  // 型がわからない
    a + b
}

// ✅ OK
fn add(a: i32, b: i32) -> i32 {
    a + b
}

3.3.3 戻り値(Return Values)

fn five() -> i32 {
    5  // 式(expression)、セミコロンなし
}

fn plus_one(x: i32) -> i32 {
    x + 1  // 最後の式が戻り値
}

fn main() {
    let x = five();
    println!("x の値: {}", x);

    let y = plus_one(5);
    println!("y の値: {}", y);
}

明示的なreturn

fn early_return(x: i32) -> i32 {
    if x < 0 {
        return 0;  // 早期リターン
    }
    x * 2
}

3.3.4 式(Expression)と文(Statement)

重要な概念

┌─────────────────────────────────────────────────────────┐
│              式(Expression)vs 文(Statement)          │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  式(Expression)                                        │
│    ├─ 値を評価する                                       │
│    ├─ セミコロンなし                                     │
│    └─ 例:5 + 6、関数呼び出し、ブロック                  │
│                                                         │
│  文(Statement)                                         │
│    ├─ アクションを実行                                   │
│    ├─ セミコロンあり                                     │
│    └─ 例:let、関数定義                                  │
│                                                         │
└─────────────────────────────────────────────────────────┘

fn main() {
    let y = {
        let x = 3;
        x + 1  // 式(セミコロンなし)→ 4が返る
    };
    println!("y の値: {}", y); // 4

    // ❌ セミコロンをつけると文になる
    let z = {
        let x = 3;
        x + 1;  // 文(セミコロンあり)→ ()が返る
    };
    // zの型は()(unit型)
}

---

3.4 コメント

3.4.1 通常のコメント

// 1行コメント

/* ブロックコメント
   複数行にわたる
   コメント */

fn main() {
    let x = 5; // 行末コメント
}

3.4.2 ドキュメントコメント

/// この関数は2つの数値を加算します。
///
/// # Examples
///
/// 
/// let result = add(2, 3); /// assert_eq!(result, 5); ///
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

//! クレート全体のドキュメント
//! (lib.rsの先頭で使用)

---

3.5 制御フロー

3.5.1 if式

fn main() {
    let number = 7;

    if number < 5 {
        println!("条件は真");
    } else {
        println!("条件は偽");
    }
}

if式は値を返す

let condition = true;
let number = if condition { 5 } else { 6 };
//                          ^        ^
//                          式       式(型が一致)

println!("number: {}", number); // 5

elseif

let number = 6;

if number % 4 == 0 {
    println!("4で割り切れる");
} else if number % 3 == 0 {
    println!("3で割り切れる");
} else if number % 2 == 0 {
    println!("2で割り切れる");
} else {
    println!("4, 3, 2のいずれでも割り切れない");
}

注意:条件は bool 型でなければならない

// ❌ エラー
let number = 3;
if number {  // 型が一致しない
    println!("number is non-zero");
}

// ✅ OK
if number != 0 {
    println!("number is non-zero");
}

3.5.2 loop(無限ループ)

loop {
    println!("無限に繰り返す");
    // Ctrl+C で停止
}

breakで抜ける

let mut counter = 0;

loop {
    counter += 1;

    if counter == 10 {
        break;
    }
}
println!("counter: {}", counter); // 10

ループから値を返す

let mut counter = 0;

let result = loop {
    counter += 1;

    if counter == 10 {
        break counter * 2;  // 20を返す
    }
};

println!("result: {}", result); // 20

ループラベル

'outer: loop {
    println!("外側のループ");

    'inner: loop {
        println!("内側のループ");
        break 'outer;  // 外側のループを抜ける
    }
}

3.5.3 while(条件付きループ)

let mut number = 3;

while number != 0 {
    println!("{}!", number);
    number -= 1;
}

println!("発射!");

3.5.4 for(イテレータループ)

// 配列のイテレーション
let a = [10, 20, 30, 40, 50];

for element in a {
    println!("値: {}", element);
}

// 範囲(Range)
for number in 1..4 {  // 1, 2, 3(4は含まない)
    println!("{}", number);
}

for number in 1..=4 {  // 1, 2, 3, 4(4を含む)
    println!("{}", number);
}

// 逆順
for number in (1..4).rev() {  // 3, 2, 1
    println!("{}", number);
}

whileとforの比較

// ❌ whileは境界チェックが毎回発生
let a = [10, 20, 30, 40, 50];
let mut index = 0;

while index < a.len() {
    println!("値: {}", a[index]);
    index += 1;
}

// ✅ forは高速かつ安全
for element in a {
    println!("値: {}", element);
}

---

3.6 型変換とキャスト

3.6.1 as キャスト

let x: i32 = 42;
let y: f64 = x as f64;  // i32 → f64

let a: u8 = 255;
let b: i8 = a as i8;    // -1(オーバーフロー)

// 危険なキャスト
let x: f64 = 300.5;
let y: u8 = x as u8;    // 44(切り捨て + オーバーフロー)

3.6.2 From / Into トレイト

より安全な変換:

let x: i32 = 42;
let y: i64 = i64::from(x);  // From

let x: i32 = 42;
let y: i64 = x.into();      // Into(型推論が必要)

---

3.7 実践例

例1:FizzBuzz

fn main() {
    for number in 1..=100 {
        if number % 15 == 0 {
            println!("FizzBuzz");
        } else if number % 3 == 0 {
            println!("Fizz");
        } else if number % 5 == 0 {
            println!("Buzz");
        } else {
            println!("{}", number);
        }
    }
}

例2:フィボナッチ数列

fn fibonacci(n: u32) -> u32 {
    if n == 0 {
        return 0;
    } else if n == 1 {
        return 1;
    }

    let mut a = 0;
    let mut b = 1;

    for _ in 2..=n {
        let temp = a + b;
        a = b;
        b = temp;
    }

    b
}

fn main() {
    for i in 0..10 {
        println!("fibonacci({}) = {}", i, fibonacci(i));
    }
}

---

3.8 まとめ

学んだこと

  • 変数と可変性
- デフォルトで不変 - mut で可変化 - シャドーイング

  • データ型
- スカラー型:整数、浮動小数点、ブール、文字 - 複合型:タプル、配列

  • 関数
- 引数と戻り値 - 式と文の違い

  • 制御フロー
- if式 - loop、while、for

次のステップ

次の章では、Rustの核心である所有権システムを学びます。これはRustを他の言語と決定的に区別する概念です。

---

参考資料