基礎
変数と可変性
Rust の変数はデフォルトで不変です — これは偶発的な変更を防ぐコアの安全機能です。値を本当に変更する必要がある場合のみ 'mut' を使用してください。'const' は明示的な型を必要としコンパイル時にインライン化され、'static' は固定メモリアドレスと 'static ライフタイムを持ちます。
let x = 5; // immutable by default
let mut y = 10; // mutable with 'mut'
y += 1; // OK, y is mutable
const MAX: u32 = 100; // compile-time constant
static GREETING: &str = "Hi"; // global static
let z: i32 = -5; // explicit type annotationシャドーイング
シャドーイングにより変数名を再利用しながら型や値を変更できます。'mut' とは異なり、シャドーイングは新しいバインディングを作成します — 新しい名前を考案せずにデータを変換(例:文字列を int に解析)するのに有用です。古い値は新しいバインディング後にドロップされます。
let x = 5;
let x = x * 2; // shadow previous x, now 10
let x = "text"; // can even change type!
println!("{}", x); // "text"
// shadowing vs mut: shadowing creates a NEW variable
// with the same name, allowing type changesコメントと出力
アイテムドキュメントには /// を('cargo doc' で表示)、モジュール/クレートドキュメントには //! を使用します。println! は stdout へ、eprintln! は stderr へ書き込みます。{} プレースホルダはフォーマット指定をサポートします:> は右揃え、< は左揃え、^ は中央、.N は精度、#b/#o/#x は2進/8進/16進です。
// Line comment
/* Block comment */
/// Doc comment (renders in rustdoc)
//! Module-level doc comment
let name = "Alice";
println!("Hello, {}!", name); // format string
println!("{0} {1} {0}", "a", "b"); // positional args
println!("{:>5}", 42); // right-align, width 5
println!("{:.2}", 3.14159); // 2 decimal places: 3.14
println!("{:#b}", 0b1010); // binary: 0b1010
eprintln!("Error to stderr"); // error outputデータ型の概要
Rust には暗黙の型変換がありません — キャストには 'as' を使用します。i32 がデフォルトの整数型、f64 がデフォルトの浮動小数点型です。usize はインデックスとサイズに使用されます。char は完全な Unicode スカラー値(バイトではない)なので、'🦀' は単一の char です。
// Scalar types
let a: i32 = 42; // signed 32-bit integer
let b: u64 = 100; // unsigned 64-bit
let c: f64 = 3.14; // 64-bit float (default)
let d: bool = true;
let e: char = '🦀'; // 4-byte Unicode scalar
// isize/usize: pointer-sized (32 or 64 bit depending on arch)
let len: usize = vec![1,2,3].len();
// Compound types
let pair: (i32, &str) = (1, "hello");
let arr: [i32; 3] = [0; 3]; // [0, 0, 0]型キャストとエイリアス
'as' キャストはチェックなしでデータを失う可能性があります(例:300u16 as u8 は 44 にラップ)。安全な変換には From/Into トレイトを使用し、これらは損失のないキャストに実装されています。型エイリアスは可読性を向上させますが新しい型は作成しません — 個別の newtype パターンには 'struct NewType(i32)' を使用してください。
// 'as' keyword for primitive casts (may truncate)
let x: i32 = 42;
let y: f64 = x as f64; // 42.0
let z: u8 = 300u16 as u8; // truncated: 44
// From/Into traits for safe conversion
let s = String::from("hi");
let owned: String = "hi".into();
// Type aliases
type Kilometers = i32;
let distance: Kilometers = 5;文字列
String と &str
String(所有、ヒープ)と &str(借用スライス)の区別は Rust で基本的です。テキストを所有/変更/成長させる必要がある場合は String を使用し、読み取るだけの場合は &str を使用します。&str は String のバッファまたはバイナリ内の文字列リテラルのいずれかを指せます。柔軟性のため関数パラメータでは &str を優先してください。
// &str: immutable string slice (borrowed)
let s1: &str = "literal"; // stored in binary
let s2: &str = &s3[..2]; // slice of a String
// String: owned, growable, heap-allocated
let mut s3: String = String::from("hello");
s3.push_str(", world"); // append
s3.push('!'); // append char
s3.insert(0, '>'); // insert at index
println!("{}", s3); // >hello, world!
// Convert: &str -> String
let owned = "literal".to_string();
let owned2 = String::from("literal");文字列メソッド
String メソッドはインプレースで変更する代わりに新しい所有 String を返します(可変 String の push_*/insert メソッドを除く)。split() はイテレータを返すため、.collect() で実体化します。UTF-8 境界がバイトインデックスと整列しないため、s[0] のインデックス付けは許可されません — 代わりに s.chars().nth(0) を使用してください。
let s = String::from("Hello, World");
// inspection
println!("len: {}", s.len()); // 12
println!("is_empty: {}", s.is_empty()); // false
println!("contains 'World': {}", s.contains("World")); // true
println!("starts_with 'Hello': {}", s.starts_with("Hello")); // true
// transformation
let upper = s.to_uppercase(); // HELLO, WORLD
let replaced = s.replace("o", "0"); // Hell0, W0rld
let trimmed = " hi ".trim().to_string(); // hi
// splitting
for word in s.split(", ") {
println!("{}", word); // Hello / World
}
let parts: Vec<&str> = s.split_whitespace().collect();フォーマットと連結
+ 演算子は左の String の所有権を取り、右(&str)を借用します。これが s1 + &s2 の後 s1 が無効になる理由です。複数文字列の連結には format! を優先してください — より読みやすく、どのオペランドも移動しません。concat! はリテラルのみで動作し &'static str を生成します。
// format! macro creates a new String
let name = "Alice";
let msg = format!("Hi {}, you have {} messages", name, 5);
// concatenation
let s1 = String::from("Hello");
let s2 = String::from(" World");
let s3 = s1 + &s2; // s1 moved, s2 borrowed
// s1 is now invalid!
let s4 = format!("{}{}", s2, s3); // neither moved
// concat! macro for literals
let s5 = concat!("foo", "bar"); // "foobar" at compile time文字とバイトの反復
Rust の文字列は UTF-8 エンコードのため、バイトインデックス != 文字インデックスです。chars() は Unicode スカラー値を反復し(デコードに O(n))、bytes() は生バイトを反復します。[n..m] でのスライスは n または m がマルチバイト文字の中間にある場合パニックします。バイトレベルアクセスには as_bytes() で Vec<u8> に変換してください。
let s = "héllo"; // é is 2 bytes in UTF-8
// iterate over chars (Unicode scalar values)
for c in s.chars() {
print!("{} ", c); // h é l l o
}
// iterate over bytes
for b in s.bytes() {
print!("{} ", b); // 104 195 169 108 108 111
}
// get char at index (O(n) — must walk UTF-8)
let third = s.chars().nth(2); // Some('l')
// string slices must be on char boundaries
let slice = &s[0..1]; // "h" — OK
// let bad = &s[0..2]; // PANIC if mid-é!解析と変換
parse() は文字列が有効な数値でない可能性があるため Result を返します — 常にエラーを処理してください。ターボフィッシュ構文 parse::<T>() で型をインライ ン指定できます。String から &str への変換は無料(単なる借用)ですが、&str から String への変換はアロケートします。collect() で char のイテレータから String を構築できます。
// String/str -> number
let n: i32 = "42".parse().unwrap();
let n2 = "42".parse::<i32>().unwrap(); // turbofish syntax
let f: f64 = "3.14".parse().unwrap();
// number -> String
let s = 42.to_string();
let s2 = format!("{}", 42);
// String -> &str (free, just dereference)
let owned = String::from("hi");
let borrowed: &str = &owned;
// collect chars into String
let upper: String = "hello".chars().map(|c| c.to_uppercase().next().unwrap()).collect();データ構造
配列とスライス
配列 [T; N] はコンパイル時に既知の固定サイズでスタックに存在します。スライス &[T] は連続するシーケンスを借用するファットポインタ(ポインタ + 長さ)です — 関数がサイズを問わず任意の配列やベクタを受け取ることを可能にします。汎用性のため関数シグネチャにはスライスを使用してください。
// Fixed-size array (stack allocated)
let arr: [i32; 3] = [1, 2, 3];
let zeros = [0; 5]; // [0, 0, 0, 0, 0]
println!("first: {}", arr[0]);
println!("len: {}", arr.len());
// Slice: a view into an array/vector
let slice: &[i32] = &arr[1..3]; // [2, 3]
let full: &[i32] = &arr; // whole array
let first = &arr[..1]; // [1]
// iterating
for n in &arr {
println!("{}", n);
}ベクタ(Vec<T>)
Vec<T> は Rust の成長可能な配列で、容量が倍増するヒープメモリに支えられています。push/pop は償却 O(1)、insert/remove は要素がシフトするため O(n) です。安全なアクセスには v[i] の代わりに .get(i) を使用してください(Option を返す)。into_iter() はベクタを消費し、所有値を生成します。
// growable heap array
let mut v: Vec<i32> = Vec::new();
let v2 = vec![1, 2, 3]; // macro shorthand
v.push(4); // append
v.pop(); // remove last -> Option
v.insert(0, 0); // insert at index (O(n))
v.remove(0); // remove at index (O(n))
v.extend([5, 6]); // append multiple
// access
println!("{}", v[0]); // panics if out of bounds
println!("{:?}", v.get(0)); // Some(&4) — safe
// iterate by value, ref, or mut ref
for n in &v { print!("{}", n); }
for n in &mut v { *n *= 2; } // double each
let owned: Vec<i32> = v.into_iter().collect();HashMap と BTreeMap
HashMap はハッシュ化により平均 O(1) アクセスを提供しますが順序付けなし。BTreeMap は B ツリーで O(log n) アクセスだがキーをソート済みに保ちます。高速ルックアップには HashMap を、順序付き反復や範囲クエリには BTreeMap を使用します。entry().or_insert() が 'upsert' の慣用的な方法 — 値への可変参照を返し、不在ならデフォルトを挿入します。
use std::collections::HashMap;
use std::collections::BTreeMap;
// HashMap: O(1) average lookup, unordered
let mut scores: HashMap<String, i32> = HashMap::new();
scores.insert(String::from("Alice"), 10);
scores.entry("Bob".into()).or_insert(0); // insert if absent
let alice = scores.get("Alice"); // Some(&10)
// iterate (unordered)
for (name, score) in &scores {
println!("{}: {}", name, score);
}
// BTreeMap: O(log n) lookup, sorted by key
let mut bt: BTreeMap<String, i32> = BTreeMap::new();
bt.insert("zebra".into(), 1);
bt.insert("apple".into(), 2);
// iterates in sorted order: apple, zebraHashSet と BTreeSet
HashSet は O(1) メンバーシップチェックで一意の値を格納します。集合演算(和、積、差、対称差)はイテレータを返します。重複排除、メンバーシップテスト、数学的集合演算にセットを使用します。BTreeSet は BTreeMap に支えられたソート済みの等価物です。