第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 で可変化
- シャドーイング- データ型
- 関数
- 制御フロー
次のステップ
次の章では、Rustの核心である所有権システムを学びます。これはRustを他の言語と決定的に区別する概念です。
---