この記事の要点
• 所有権システムでメモリ安全性を保証、ガベージコレクタ不要
• Result<T, E>とOption<T>でエラーと欠損を型で表現
• 借用とライフタイムで参照の安全性をコンパイル時に検証
基本文法
| 構文 | 説明 |
|---|---|
let x = 5; | 不変変数の宣言 |
let mut y = 10; | 可変変数の宣言 |
const MAX: u32 = 100; | 定数の宣言 |
fn add(a: i32, b: i32) -> i32 | 関数定義 |
if x > 0 { ... } else { ... } | 条件分岐 |
loop { ... } | 無限ループ |
while x < 10 { ... } | 条件付きループ |
for i in 0..10 { ... } | 範囲のループ |
ポイント: Rustの変数はデフォルトで不変です。可変にする場合は明示的にmutキーワードを付けます。これにより意図しない変更を防ぎます。
所有権ルール
| ルール | 説明 |
|---|---|
| 各値は1つのオーナーを持つ | オーナーがスコープを抜けると値は破棄される |
| オーナーは1つだけ | 同時に複数の所有者は存在できない |
let y = x; でムーブ | xの所有権がyに移り、xは無効化 |
let y = x.clone(); でコピー | ヒープデータを深くコピーして新しい所有者を作成 |
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1の所有権がs2にムーブ
// println!("{}", s1); // エラー: s1は無効
println!("{}", s2); // OK
}
注意: StringやVecなどヒープを使う型は、代入時に所有権がムーブします。元の変数は使えなくなるため、継続利用したい場合はclone()か借用を使います。
借用と参照
| 構文 | 説明 |
|---|---|
&x | 不変参照(借用) |
&mut x | 可変参照 |
*r | 参照の逆参照 |
| ルール | 説明 |
|---|---|
| 不変参照は複数同時OK | &x, &x, &x… |
| 可変参照は1つだけ | &mut xが存在する間、他の参照は作れない |
| 不変と可変は混在不可 | &xが存在する間、&mut xは作れない |
fn main() {
let mut s = String::from("hello");
let r1 = &s; // OK
let r2 = &s; // OK
println!("{}, {}", r1, r2);
let r3 = &mut s; // OK: r1, r2はもう使われない
r3.push_str(" world");
}
実践メモ: 借用チェッカはコンパイル時に参照の競合を検出します。エラーが出たら、参照のスコープを短くするか、clone()で所有権を複製するかを検討します。
ライフタイム
| 構文 | 説明 |
|---|---|
fn foo<'a>(x: &'a str) | ライフタイムパラメータ'aを宣言 |
&'a T | ライフタイム'aを持つ参照 |
struct S<'a> { r: &'a i32 } | 構造体フィールドに参照を持つ場合 |
'static | プログラム全体で有効なライフタイム |
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
ポイント: ライフタイムは参照がどれだけ有効かをコンパイラに伝える注釈です。戻り値が参照の場合、引数のライフタイムと紐付けることでダングリングポインタを防ぎます。
エラー処理
| 型 | 説明 |
|---|---|
Result<T, E> | 成功値Ok(T)または失敗値Err(E) |
Option<T> | 値Some(T)または欠損None |
unwrap() | Ok/Someの値を取り出し、エラーならパニック |
expect("msg") | unwrap()にメッセージを付加 |
? 演算子 | エラーを呼び出し元に早期リターン |
use std::fs::File;
use std::io::Read;
fn read_file(path: &str) -> Result<String, std::io::Error> {
let mut file = File::open(path)?; // エラーなら即リターン
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
実践メモ: ? 演算子はmatchやunwrap()よりも簡潔でエラー伝播に適しています。関数の戻り値が Result のときだけ使えます。
構造体とトレイト
| 構文 | 説明 |
|---|---|
struct Point { x: i32, y: i32 } | 構造体定義 |
impl Point { fn new() -> Self {...} } | メソッド実装 |
trait Draw { fn draw(&self); } | トレイト定義 |
impl Draw for Point { ... } | トレイト実装 |
#[derive(Debug, Clone)] | 自動トレイト導出 |
#[derive(Debug)]
struct User {
name: String,
age: u8,
}
impl User {
fn new(name: String, age: u8) -> Self {
User { name, age }
}
}
ポイント: #[derive]属性でDebug, Clone, PartialEqなどの一般的なトレイトを自動実装できます。手動実装よりも高速かつ安全です。
パターンマッチ
| 構文 | 説明 |
|---|---|
match x { ... } | すべてのパターンを網羅する必要がある |
Some(v) => ... | Optionのマッチ |
Ok(v) => ... | Resultのマッチ |
_ => ... | すべての残りのケース |
if let Some(v) = x { ... } | 単一パターンのマッチ |
fn describe(x: Option<i32>) {
match x {
Some(v) if v > 0 => println!("正の数: {}", v),
Some(0) => println!("ゼロ"),
Some(_) => println!("負の数"),
None => println!("値なし"),
}
}
注意: matchは網羅性チェックがあるため、すべてのケースを処理しないとコンパイルエラーになります。漏れを防ぐために_で残りをキャッチします。
よく使うマクロ
| マクロ | 説明 | 例 |
|---|---|---|
println!("x={}", x) | フォーマット出力 | - |
vec![1, 2, 3] | ベクタ生成 | let v = vec![1, 2, 3]; |
panic!("error") | パニック発生 | - |
assert!(x == 5) | アサーション | - |
assert_eq!(x, 5) | 等価性アサーション | assert_eq!(v.len(), 3); |
Cargo コマンド
| コマンド | 説明 |
|---|---|
cargo new project_name | 新規プロジェクト作成 |
cargo build | ビルド(デバッグ) |
cargo build --release | リリースビルド |
cargo run | ビルド&実行 |
cargo test | テスト実行 |
cargo check | 型チェックのみ(高速) |
cargo doc --open | ドキュメント生成&表示 |
実践メモ: cargo checkはコンパイルせず型チェックだけ行うので、開発中の確認が高速です。編集→チェック→修正のサイクルで活用できます。
参考リソース
- The Rust Programming Language - 公式入門書
- Rust by Example - サンプルコード集
- Rust Standard Library - 標準ライブラリリファレンス
- Rustlings - 演習問題集