Rust 構文の基礎
3部構成のリファレンス:問題解決のための言語コア、アルゴリズムパターンテンプレート、パズルから実バックエンド開発への橋渡け。ここのすべてのコードはコンパイルおよび検証されています。
目次
Part 1 — 構文とデータ構造
構文、慣用句、問題で実際に用いるデータ構造パターン。
1. 変数とスカラー型
let x = 5; // immutable
let mut y = 5; // mutable
let z: i64 = 10; // explicit type
const MAX: u32 = 100; // compile-time constant
| 型 | 注記 |
i32 / i64 | 符号付き整数(デフォルトは i32) |
u32 / u64 | 符号なし整数 |
usize | インデックス/長さの型 — すべての .len() とインデックス |
f64 | 浮動小数点数(除算/床関数に使用) |
bool / char | true · 'a'(4バイトのunicode) |
警告: LeetCodeの第1の落とし穴: インデックスは
usizeだが答えは通常i32。asで自由にキャストできます:(end - start) as i32。
2. キャストと数値の裏技
let a = 7 as i64; // numeric cast
let h = (n as f64 / 2.0).floor() as i32; // floor division
let big = i32::MAX; // and i32::MIN
let q = 17 / 5; // 3 (integer div)
let r = 17 % 5; // 2 (mod)
let p = i64::pow(2, 10); // 1024
let ab = (-5).abs();
| 演算 | 意味 |
a.max(b) | 大きい方 |
a.min(b) | 小さい方 |
std::cmp::min(a, b) | 同じ、自由関数形式 |
a.saturating_sub(b) | アンダーフローパニックなしで減算 |
警告: アンダーフローがパニックする!
0usize - 1はクラッシュします。ループをwhile right > leftで保護するか、checked_sub/saturating_subを使用してください。
3. 文字列と文字
String = 所有可能/拡張可能 · &str = 借用スライス。文字列を位置でインデックスできません — 文字を通じて走査してください。
// LeetCodeの動き:文字列をVec\<char>に変換
let v: Vec\\<char> = s.chars().collect();
let n = s.len(); // bytes!
let n = s.chars().count(); // real char count
s.chars().nth(i) // Option\\<char>, O(n)
v[i] // O(1) on the Vec
let mut out = String::new();
out.push('a');
out.push_str("bc");
out.trim_end().to_string()
分割と構築
s.split_whitespace() // words
s.split(',')
v.iter().collect::\\<String>() // chars → String
"x".repeat(3) // "xxx"
文字テストと計算
c.is_alphabetic(); c.is_numeric();
c.is_alphanumeric(); c.is_whitespace();
c.to_ascii_lowercase();
c.to_digit(10); // '7' → Some(7)
(c as u8 - b'a') as usize; // 'a'..'z' → 0..25
4. Vec — 主力選手
let mut v: Vec\\<i32> = Vec::new();
let v = vec![1, 2, 3];
let v = vec![0; n]; // n zeros
// 2Dグリッド(DPテーブル!)
let dp = vec![vec![0; cols]; rows];
| メソッド | 意味 |
v.push(x) | 追加 |
v.pop() | 末尾から Option\\<T> を取得 |
v.len() / v.is_empty() | サイズチェック |
v.last() / v.first() | Option<&T> |
v.contains(&x) | 線形探索 |
v.reverse() | インプレース |
v.swap(i, j) | 2つのインデックスをスワップ |
v[i..j] | スライス(半開区間) |
ソート
v.sort(); // ascending
v.sort_by(|a, b| b.cmp(a)); // descending
v.sort_by_key(|x| x.0); // by field
v.sort_unstable(); // faster, no stable-tie guarantee
v.dedup(); // drop adjacent dups
警告: 比較子での借用:
a.cmp(&b)、a.0.cmp(&b.0)— クロージャはあなたに参照を渡します。
5. イテレータ(Rustで大きな存在)
アダプタをチェーンして、.collect() または値にフォールドします。消費されるまで遅延評価されます。
v.iter() // &T (read)
v.iter_mut() // &mut T (edit in place)
v.into_iter() // T (consume / move)
| アダプタ | 意味 |
.map(|x| x*2) | 各要素を変換 |
.filter(|&x| x>0) | マッチする要素を保持 |
.sum() / .product() | 注釈:.sum::\\<i32>() |
.max() / .min() | → Option\\<T> |
.count() | いくつあるか |
.rev() | 方向を逆転 |
.enumerate() | → (index, val) |
.zip(other) | 2つのイテレータをペアアップ |
.position(|x| ..) | 最初にマッチするインデックス |
.any / .all(..) | → bool |
.fold(init, |a,x| ..) | 累積 |
let sum: i32 = v.iter().sum();
let sq: Vec\\<i32> = v.iter().map(|x| x * x).collect();
let evens = v.iter().filter(|&&x| x % 2 == 0).count();
6. HashMap と HashSet
use std::collections::HashMap;
let mut m: HashMap<char, i32> = HashMap::new();
頻度計数(古典的な方法)
// idiom A — or_insert
*m.entry(c).or_insert(0) += 1;
// idiom B — and_modify
m.entry(c)
.and_modify(|v| *v += 1)
.or_insert(1);
| メソッド | 戻り値 / 効果 |
m.get(&k) | Option<&V> |
m.get_mut(&k) | 値への可変参照 |
m.contains_key(&k) | bool |
m.insert(k, v) | 追加 / 上書き |
m.remove(&k) | 削除 |
m.keys() / m.values() | イテレート |
m.len() / m.clear() | サイズ / クリア |
HashSet — 重複排除とメンバーシップ
use std::collections::HashSet;
let mut seen: HashSet\\<i32> = HashSet::new();
if !seen.insert(x) { /* was already present — a dup! */ }
seen.contains(&x);
ヒント: 編集しながらイテレート:
get_mutで最初に借用するか、キーを収集してから変異させてください — イテレータを保持しながら同時にマップを変異させることはできません。
7. BinaryHeap(優先度キュー)
BinaryHeap はデフォルトでは最大ヒープです。最小ヒープの場合、値を Reverse でラップしてください。
use std::collections::BinaryHeap;
use std::cmp::Reverse;
// max-heap
let mut h: BinaryHeap\\<i32> = piles.into_iter().collect();
h.push(5);
while let Some(top) = h.pop() { /* ... */ }
// min-heap via Reverse
let mut mh: BinaryHeap<Reverse\\<i32>> = BinaryHeap::new();
mh.push(Reverse(5));
if let Some(Reverse(min)) = mh.pop() { /* ... */ }
| メソッド | 意味 |
h.peek() | トップを確認、Option<&T> |
h.pop() | トップを削除 |
h.len() | サイズ |
ヒント: ヒープのペアの場合、
(priority, item)は最初のフィールドでソートされます — タプルを適切に順序付けしてください。
8. VecDeque(キュー / BFS)
use std::collections::VecDeque;
let mut q: VecDeque\\<i32> = VecDeque::new();
q.push_back(1); // enqueue
q.push_front(0);
q.pop_front(); // dequeue → Option
q.pop_back();
q.front(); q.back(); q.len();
警告: BFSスケルトン はここにあります — キューを前線として、訪問済みのために
HashSet。
while let Some(node) = q.pop_front() {
for nb in neighbors(node) {
if visited.insert(nb) {
q.push_back(nb);
}
}
}
9. 制御フロー とループ
if x > 0 { } else if x < 0 { } else { }
// if is an expression
let sign = if x >= 0 { 1 } else { -1 };
for i in 0..n { } // 0..n-1
for i in 0..=n { } // inclusive
for i in (0..n).rev() { } // reverse
for (i, x) in v.iter().enumerate() { }
while left < right { }
loop { break; } // infinite
match — パターンの力
match x {
0 => "zero",
1 | 2 => "small",
3..=9 => "mid",
_ => "big", // catch-all required
}
10. Option と Result
Rustには null がありません。おそらく値は Option\\<T> = Some(x) / None。
if let Some(x) = v.last() { use(x); }
match m.get(&k) {
Some(v) => { },
None => { },
}
| メソッド | 意味 |
.unwrap() | 値を取得または パニック |
.unwrap_or(d) | 値またはデフォルト |
.unwrap_or_default() | 値または 0 / "" / … |
.is_some() / .is_none() | 存在チェック |
.map(|x| ..) | 存在する場合は変換 |
ヒント: LeetCodeでは
.unwrap()は通常、値が存在することが証明されている場合は問題ありません(例:len() > 0チェック後)。
11. 所有権と借用
あなたと戦う部分。各値は1つの所有者を持ちます。それを移動するか、または参照を借用するかのどちらかです。
| 形式 | 意味 |
&x | 共有借用(読み取り、多くの場合が許可) |
&mut x | 排他的借用(一度に1つ) |
x.clone() | 深いコピー — 借用チェッカーが問題になったときの脱出ハッチ |
// pass by ref → caller keeps ownership
fn sum(v: &Vec\\<i32>) -> i32 {
v.iter().sum()
}
sum(&nums); // nums still usable after
警告: 一般的なエラー:
- 「移動後に借用」→ 代わりに
&を取るか、.clone()を使用してください。 - 「可変として借用できない」→ すでに別の借用が有効です。スコープを短くしてください。
- 2ポインタスワップが必要ですか?参照を保持するのではなく、Vecにインデックスしてください。
// swap via temp
let tmp = a.clone();
a = b;
b = tmp;
// or simply:
std::mem::swap(&mut a, &mut b);
12. 関数とクロージャ
fn add(a: i32, b: i32) -> i32 {
a + b // no semicolon = return
}
fn noop() { } // returns ()
// nested fn (no captures) — handy in solutions
fn helper(v: &Vec\\<i32>) -> i32 { /* .. */ }
// closures CAN capture environment
let k = 10;
let f = |x: i32| x + k;
let g = |a, b| a * b;
ヒント: LeetCodeメソッドシグネチャは
impl Solution内でpub fn name(&self, ...)を使用します。selfパラメータはそこにあるだけです — ロジックはボディに入ります。
13. パターン:2ポインタとスライディングウィンドウ
// two pointers from both ends (palindrome / pair sum)
let (mut left, mut right) = (0usize, v.len() - 1);
while left < right {
if v[left] + v[right] == target { break; }
else if v[left] + v[right] < target { left += 1; }
else { right -= 1; }
}
// sliding window with a moving start
let (mut start, mut best) = (0usize, 0i32);
for end in 0..v.len() {
while window_invalid(start, end) { start += 1; }
best = best.max((end - start + 1) as i32);
}
警告:
right -= 1をrightが0の場合のusizeアンダーフローから保護してください。while left < right条件は通常あなたを救います。
14. パターン:2D DPテーブル
// e.g. Longest Common Subsequence — fill from bottom-right
let (a, b): (Vec\\<char>, Vec\\<char>) =
(s1.chars().collect(), s2.chars().collect());
let mut dp = vec![vec![0; b.len() + 1]; a.len() + 1];
for i in (0..a.len()).rev() {
for j in (0..b.len()).rev() {
dp[i][j] = if a[i] == b[j] {
dp[i + 1][j + 1] + 1
} else {
dp[i + 1][j].max(dp[i][j + 1])
};
}
}
// answer at dp[0][0]
ヒント: テーブルを
(n+1) × (m+1)にサイズ調整し、ゼロボーダーを使用すると、バウンズチェックなしでi+1/j+1を読み取ることができます。上記の前方参照の漸化式の場合は.rev()で反復処理するか、後方参照の場合は通常の順序で反復処理してください。
15. パターン:スタック(プレーンなVec)
let mut stack: Vec\\<char> = Vec::new();
for c in s.chars() {
if c == '(' {
stack.push(c);
} else if stack.last() == Some(&'(') {
stack.pop();
} else {
stack.push(c);
}
}
let leftover = stack.len() as i32;
ヒント:
stack.last()はOption<&T>を返すため、Some(&val)と比較してください — パニックのリスクがあるため.unwrap()は必要ありません。
16. トップの落とし穴と修正
| 問題 | 修正 |
| usizeアンダーフロー | 減算を保護する;saturating_sub を使用 |
| 型不一致 | インデックス = usize、戻り値 = i32;as でキャスト |
| 移動された値 | & を借用するか .clone() してください |
| 可変 + 不可変借用 | スコープを分割;参照ではなくインデックスを取得 |
| Stringをインデックスできない | 最初に .chars().collect::<Vec<_>>() してください |
| sumは型が必要 | let s: i32 = it.sum(); |
| 整数オーバーフロー | 合計が大きくなるときは i64 を使用 |
| 浮動小数点床 | f64 にキャスト、.floor() を実行、キャストバック |
デバッグ印刷
println!("{}", x); // Display
println!("{:?}", v); // Debug (Vec, tuple)
println!("{:#?}", m); // pretty Debug
eprintln!("err {}", e); // stderr
17. 構造を選択
| 必要 | 取得するもの |
| 順序付きリスト、インデックス可能 | Vec\\<T> |
| キーでカウント / ルックアップ | HashMap |
| 以前見たことがある / ユニーク | HashSet |
| 常に最大値/最小値をポップ | BinaryHeap |
| FIFOキュー / BFS | VecDeque |
| LIFO / マッチング | スタックとしての Vec |
| 固定グリッド / DP | vec![vec![..]] |
| ソート済みのユニークキー | BTreeMap / BTreeSet |
cargo new prob · cargo run · cargo test
Part 2 — アルゴリズムパターンとテンプレート
スケルトン — ロジックをドロップして条件を変更して、ソリューションを発送してください。
1. バイナリサーチ(境界)
半開 [lo, hi) はほとんどのオフバイワンを避けます。mid はオーバーフローを回避するために計算されます。
完全一致
fn search(v: &[i32], target: i32) -> i32 {
let (mut lo, mut hi) = (0usize, v.len());
while lo < hi {
let mid = lo + (hi - lo) / 2;
if v[mid] == target {
return mid as i32;
} else if v[mid] < target {
lo = mid + 1;
} else {
hi = mid;
}
}
-1
}
最左 / 下限
// first index where v[i] >= target
let (mut lo, mut hi) = (0usize, v.len());
while lo < hi {
let mid = lo + (hi - lo) / 2;
if v[mid] < target { lo = mid + 1; }
else { hi = mid; }
}
// lo is the insertion point
ヒント: 標準ショートカット:
v.binary_search(&t)→Ok(i)見つかった場合、Err(i)= 挿入場所。またpartition_point(|&x| x < t)。
2. 答えに対するバイナリサーチ
質問が「実行可能な最小X」である場合 — 配列ではなく答えスペースを検索してください。
let (mut lo, mut hi) = (min_ans, max_ans);
while lo < hi {
let mid = lo + (hi - lo) / 2;
if feasible(mid) {
hi = mid; // try smaller
} else {
lo = mid + 1; // need bigger
}
}
// lo == smallest feasible answer
fn feasible(x: i64) -> bool {
// greedy check in O(n)
true
}
ヒント: 古典的な用途:ココはバナナを食べており、配列を分割する最大合計、D日以内に船舶パッケージ。トリックは
feasibleを書くことです。
3. BFS(最短経路 / レベル)
use std::collections::{VecDeque, HashSet};
let mut q: VecDeque\\<i32> = VecDeque::new();
let mut seen: HashSet\\<i32> = HashSet::new();
q.push_back(start);
seen.insert(start);
let mut steps = 0;
while !q.is_empty() {
// process one whole level
let level_size = q.len();
for _ in 0..level_size {
let node = q.pop_front().unwrap();
if node == goal { return steps; }
for nb in neighbors(node) {
if seen.insert(nb) {
q.push_back(nb);
}
}
}
steps += 1;
}
警告:
seen.insert(x)は既に存在する場合はfalseを返します — 1つの呼び出しがチェックとマークの両方を行います。キューの重複を避けるために、デキューではなく エンキュー でマークしてください。
4. グリッド走査(4方向)
let (rows, cols) = (grid.len(), grid[0].len());
let dirs = [(-1i32, 0i32), (1, 0), (0, -1), (0, 1)];
for (dr, dc) in dirs {
let nr = r as i32 + dr;
let nc = c as i32 + dc;
// bounds check BEFORE casting back to usize
if nr >= 0 && nr < rows as i32
&& nc >= 0 && nc < cols as i32 {
let (nr, nc) = (nr as usize, nc as usize);
// visit grid[nr][nc]
}
}
警告: グリッドの落とし穴: 隣接を
i32として計算して-1が表現可能であり、バウンズチェック、その後usizeにキャストしてください。usize座標で直接減算しないでください。
5. DFS(再帰的)
Rustの再帰ではすべての状態を渡すか、キャプチャする必要があります。&mut 状態を取る入れ子のヘルパーが最もクリーンです。
fn dfs(
node: usize,
adj: &Vec<Vec\\<usize>>,
seen: &mut Vec\\<bool>,
) {
seen[node] = true;
for &nb in &adj[node] {
if !seen[nb] {
dfs(nb, adj, seen);
}
}
}
// call:
let mut seen = vec![false; n];
dfs(0, &adj, &mut seen);
ヒント: グリッドDFSの場合、
&mut gridを渡し、セルをインプレースでマークします('0'/falseにフリップ)別の訪問済みセットの代わりに。
6. バックトラッキング(部分集合 / パーム)
プッシュ → 再帰 → ポップ。ポップはそれをバックトラッキングにする「アンドゥ」です。
fn backtrack(
start: usize,
nums: &[i32],
path: &mut Vec\\<i32>,
out: &mut Vec<Vec\\<i32>>,
) {
out.push(path.clone()); // record
for i in start..nums.len() {
path.push(nums[i]); // choose
backtrack(i + 1, nums, path, out); // explore
path.pop(); // un-choose
}
}
let mut out = Vec::new();
backtrack(0, &nums, &mut Vec::new(), &mut out);
| バリアント | 調整 |
| 部分集合 | i+1、すべてのノードを記録 |
| 組み合わせ | i+1、深度kで記録 |
| 順列 | used[] を追跡;0から開始 |
7. Union-Find(DSU)
接続性、サイクル検出、コンポーネント計数。経路圧縮+このレイアウトは十分高速です。
struct Dsu { parent: Vec\\<usize>, rank: Vec\\<usize> }
impl Dsu {
fn new(n: usize) -> Self {
Dsu { parent: (0..n).collect(),
rank: vec![0; n] }
}
fn find(&mut self, x: usize) -> usize {
if self.parent[x] != x {
let root = self.find(self.parent[x]);
self.parent[x] = root; // compress
}
self.parent[x]
}
fn union(&mut self, a: usize, b: usize) -> bool {
let (ra, rb) = (self.find(a), self.find(b));
if ra == rb { return false; } // already joined
if self.rank[ra] < self.rank[rb] {
self.parent[ra] = rb;
} else if self.rank[ra] > self.rank[rb] {
self.parent[rb] = ra;
} else {
self.parent[rb] = ra;
self.rank[ra] += 1;
}
true
}
}
ヒント:
unionがfalseを返すことは、サイクルを意味します(両方のエンドポイントは既に1つのセット内にあります)。
8. 接頭辞の合計
一度にプリコンパイル → 任意の範囲の合計がO(1)。サイズ n+1 で先頭の0を使用してエッジケースを避けてください。
let mut pre = vec![0i64; nums.len() + 1];
for i in 0..nums.len() {
pre[i + 1] = pre[i] + nums[i] as i64;
}
// sum of nums[l..=r] (inclusive)
let range = pre[r + 1] - pre[l];
部分配列の合計 == k(ハッシュマップトリック)
use std::collections::HashMap;
let mut seen: HashMap<i64, i32> = HashMap::new();
seen.insert(0, 1); // empty prefix
let (mut sum, mut count) = (0i64, 0);
for &x in &nums {
sum += x as i64;
if let Some(&c) = seen.get(&(sum - k)) {
count += c;
}
*seen.entry(sum).or_insert(0) += 1;
}
9. トップダウン DP(メモ化)
漸化式が自然な場合、テーブル順序ではありません。Vecでサイズした状態でキャッシュします、センチネル -1 = 「未解決」。
fn solve(i: usize, memo: &mut Vec\\<i64>,
nums: &[i64]) -> i64 {
if i >= nums.len() { return 0; }
if memo[i] != -1 { return memo[i]; }
let take = nums[i]
+ solve(i + 2, memo, nums);
let skip = solve(i + 1, memo, nums);
memo[i] = take.max(skip);
memo[i]
}
// init: vec![-1; n]
ヒント: 2D状態 →
vec![vec![-1; m]; n]。HashMapキー付き状態の場合はHashMap<(usize, usize), i64>を使用してください。
10. 単調スタック
「次の大きい / より小さい要素」はO(n)で。スタックは インデックス を保持します;新しい値が順序を壊す間にポップします。
// next greater element to the right
let mut res = vec![-1; nums.len()];
let mut stack: Vec\\<usize> = Vec::new();
for i in 0..nums.len() {
while let Some(&top) = stack.last() {
if nums[i] > nums[top] {
res[top] = nums[i];
stack.pop();
} else { break; }
}
stack.push(i);
}
ヒント: 次に小さいものの比較をフリップします;
.rev()の繰り返しは前の要素バリアント用です。
11. 高速 / 低速とグリーディノート
サイクル / ミッドポイント(配列上)
let (mut slow, mut fast) = (0usize, 0usize);
while fast + 1 < n {
slow += 1;
fast += 2;
}
// slow now at the midpoint
グリーディ区間スケジュール
// sort by end, take non-overlapping
intervals.sort_by_key(|iv| iv[1]);
let (mut end, mut count) = (i32::MIN, 0);
for iv in &intervals {
if iv[0] >= end {
count += 1;
end = iv[1];
}
}
ヒント: 「最大非オーバーラップ」の場合は終了でソート;間隔をマージするための場合は開始でソート。
12. パターンを選択(シグナル別)
| 問題は言っています… | 試す |
| ソート済み配列、X を検索 | バイナリサーチ |
| 「最小/最大…そのような」 | 答えにBSを搭載 |
| 最短経路、重みなし | BFS |
| すべてのパス / コンボ / パーム | バックトラッキング |
| 接続されたグループ / サイクル | union-find |
| 範囲合計繰り返し | 接頭辞の合計 |
| 次の大きい/より小さい | 単調スタック |
| 重複する部分問題 | DP(メモ/テーブル) |
| 隣接ウィンドウ | スライディングウィンドウ |
| トップk / ストリーミング最大 | BinaryHeap |
ビッグO理論(n ≤ …)
| n ≤ | 購入可能 |
| 10–12 | O(n!) / O(2ⁿ) バックトラッキングOK |
| 5,000 | O(n²) 罰金 |
| 10⁶ | O(n) または O(n log n) が必要 |
| 10⁹ | O(log n) — バイナリサーチ |
Part 3 — バックエンドへの橋渡し
問題の解決からプログラムの構築へ — トレイト、エラー、型と共有状態。
0. これはどこに適合するか
LeetCode Rustは言語コアを教えます。このパートは、その層とリアルサービスの間の層です — パズルがあなたに決して触らせない物です。
所有権 → [トレイトとエラー] → [スマートポインタ] → Arc\\<Mutex> → async / Tokio → ウェブフレームワーク
警告: マインドセットシフト: LeetCodeでは
.unwrap()を自由に使用します。サービスでは.unwrap()はクラッシュする方法です。実際のRustはResultをあらゆる場所でスレッドし、?で伝播します。
1. 構造体とメソッド
struct User {
id: u64,
name: String,
active: bool,
}
impl User {
// associated fn (constructor convention)
fn new(id: u64, name: String) -> Self {
Self { id, name, active: true }
}
// &self = borrow (read)
fn label(&self) -> String {
format!("#{}: {}", self.id, self.name)
}
// &mut self = borrow (mutate)
fn deactivate(&mut self) {
self.active = false;
}
}
let mut u = User::new(1, "Ada".into());
u.deactivate();
ヒント:
Self= 型;self= インスタンス。メソッドは&self、&mut self、またはself(消費)を取ります。
2. Enum — バリアントでモデル化
Rustエヌムはデータを運びます。これは「複数の形の1つ」をモデル化する方法です — ドメインモデリングの主力です。
enum Event {
Click { x: i32, y: i32 }, // struct-like
Key(char), // tuple-like
Close, // unit
}
fn handle(e: Event) {
match e {
Event::Click { x, y } => { /* */ }
Event::Key(c) => { /* */ }
Event::Close => { /* */ }
}
}
ヒント:
OptionとResultは標準のEnumにすぎません。matchはすべてのバリアントをカバーする必要があります — コンパイラはそれを強制します。
3. Result と ? オペレータ
本当のRustの心臓。Result<T, E> は Ok(T) または Err(E)。? は Ok をアンラップするか、Err を早期に返します。
fn parse_sum(a: &str, b: &str)
-> Result<i32, std::num::ParseIntError> {
let x: i32 = a.parse()?; // ? = unwrap or return Err
let y: i32 = b.parse()?;
Ok(x + y)
}
// handle at the boundary
match parse_sum("2", "3") {
Ok(n) => println!("{}", n),
Err(e) => eprintln!("bad input: {}", e),
}
| ツール | 意味 |
? | エラーをコールスタックの上に伝播 |
.map_err(..) | 1つのエラータイプを別のエラータイプに変換 |
.ok() | Result → Option(エラーをドロップ) |
.expect("msg") | カスタムパニックメッセージでアンラップ |
警告: 実世界:
anyhow(アプリ)やthiserror(ライブラリ)のようなクレートは?が多くのエラータイプで機能するようにします。ここで快適になったら一度にこれらを学んでください。
4. トレイト — 共有動作
Rustのコア抽象化。トレイトは、型が提供することを約束する一連のメソッドです — インターフェイスのようなものです。
trait Shape {
fn area(&self) -> f64;
// default method (optional override)
fn describe(&self) -> String {
format!("area = {:.2}", self.area())
}
}
struct Circle { r: f64 }
impl Shape for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.r * self.r
}
}
let c = Circle { r: 2.0 };
println!("{}", c.describe());
導出 — フリーの一般的なトレイト
// auto-implement instead of hand-writing
#[derive(Debug, Clone, PartialEq)]
struct Point { x: i32, y: i32 }
// now: {:?} printing, .clone(), ==
| 導出 | 有効 |
Debug | {:?} 印刷 |
Clone | .clone() |
PartialEq | == |
Default | ::default() |
5. ジェネリクスとトレイト境界
多くの型のコードを1回書いてください。境界は「これらのトレイトを実装する任意の型 T」を言います。
// T must be comparable (Ord)
fn largest<T: Ord + Copy>(v: &[T]) -> T {
let mut m = v[0];
for &x in v {
if x > m { m = x; }
}
m
}
// "where" clause for readability
fn show\\<T>(item: T)
where T: std::fmt::Debug {
println!("{:?}", item);
}
// impl Trait — "some type that is..."
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
move |x| x + n
}
6. スマートポインタ
これらは、プレーン参照ができない所有権の問題を解決します。各々が特定の「しかし私は…する必要があります」に答えます。
| ポインタ | 使用する場合 |
Box\\<T> | ヒープ割り当て;サイズは実行時にのみ既知 / 再帰型 |
Rc\\<T> | 複数の所有者、シングルスレッド(参照カウント) |
Arc\\<T> | 複数の所有者、スレッドセーフ(アトミックカウント) |
RefCell\\<T> | 共有参照を通じて変異させる;借用チェック済み 実行時 |
Mutex\\<T> | スレッド全体で一度に1つのミュータブルアクセス |
use std::rc::Rc;
let a = Rc::new(vec![1, 2, 3]);
let b = Rc::clone(&a); // +1 owner, no deep copy
println!("{}", Rc::strong_count(&a)); // 2
// recursive type NEEDS Box for a known size
enum List { Cons(i32, Box\\<List>), Nil }
警告: 実際に使用するコンボ:
Rc<RefCell\\<T>>1つのスレッドで共有可能な可変状態;Arc<Mutex\\<T>>スレッド全体で同じです。
7. Arc<Mutex<T>> とスレッド
共有状態についての事柄。これは、所有権が2つのスレッドが1つの値を変異させることを禁止しているため存在します — 共有所有権(Arc)ロック(Mutex)を共有します。
use std::sync::{Arc, Mutex};
use std::thread;
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let c = Arc::clone(&counter);
let h = thread::spawn(move || {
// lock() → guard; unlocks when dropped
let mut num = c.lock().unwrap();
*num += 1;
});
handles.push(h);
}
for h in handles { h.join().unwrap(); }
println!("{}", *counter.lock().unwrap()); // 10
-
Arc= スレッド全体で共有可能な所有権。 -
Mutex= データを保護します;.lock()はドロップ時にロック解除される保護を返します。 -
moveはクローンの所有権をクロージャに渡します。
ヒント:
.lock().unwrap()は「止めてアンラップ」の一般的な例外です —.lock()はもう1つのスレッドがロック(「毒された」ミューテックス)を保持している間にパニックした場合にのみエラーです。非同期バックエンドでは、代わりにtokio::sync::Mutexを使用することが多いため、ロックは.await全体で保持できます。
8. Send + Sync(理由)
コンパイラが同時性の安全性を確保するために使用する2つのマーカートレイト。あなたはめったに書きません — あなたはそれらを満たすだけです。
| トレイト | 意味 |
Send | 別のスレッドに 移動 するのは安全 |
Sync | スレッド全体で 共有 するのは安全(&T) |
これが Rc はスレッド間でコンパイルされないが Arc はする理由です:Rc は Send ではありません。コンパイラはデータ競合をそれらが存在する前に検出します — エラーメッセージはどのバウンドが不足しているかを正確に伝えます。
警告: これらを暗記しないでください。 コンパイラが「
Xをスレッド間で安全に送信できない」と言ったら、通常はRc/RefCellの代わりにArc/Mutexが必要です。
9. モジュール、クレート、Cargo
# start a project / add a dependency
cargo new my_api
cargo add serde --features derive
cargo add tokio --features full
// modules organize code
mod db {
pub fn connect() { /* */ }
pub struct Pool;
}
use db::Pool; // bring into scope
db::connect(); // or call fully-qualified
| キーワード | 意味 |
pub | アイテムをそのモジュール外で表示されるようにします |
mod x; | x.rs / x/mod.rs をロード |
crate:: | クレートルートからのパス |
super:: | 親モジュールからのパス |
10. async / await(プレビュー)
バックエンドフレームワークは非同期ファーストです。async fn はランタイムが .await されるまで何もしない Future を返します(Tokio)。
async fn fetch(id: u64) -> Result<String, String> {
// ... awaits a DB / network call
Ok(format!("user {}", id))
}
#[tokio::main]
async fn main() {
let user = fetch(1).await.unwrap();
println!("{}", user);
}
-
.awaitは待機中に制御を譲ります — スレッドをブロックしません。 -
#[tokio::main]は Future を駆動するランタイムを設定します。 -
一般的なスタック:axum(ウェブ)+ sqlx(DB)+ serde(JSON)。
11. ここからの提案される順序
| ステップ | 焦点 |
| 1 | 小さなドメインをモデリングすることで構造体、enum、 match を固める |
| 2 | .unwrap() の習慣を Result + ? に変換どこでも |
| 3 | トレイト +ジェネリクス;一般的な導出 |
| 4 | スマートポインタ;Rc\\<RefCell> で何か構築 |
| 5 | Arc\\<Mutex> + thread::spawn 共有状態用 |
| 6 | async / await + Tokio基本 |
| 7 | axum で小さなJSON APIを構築 |
無料の高品質リソース
-
本 — doc.rust-lang.org/book(章10、13、15–16がこのパートにマップ)
-
Rust by Example — 実行可能なスニペット
-
Tokio チュートリアル — 非同期にヒットしたら
このリファレンスのすべてのコードはRust 2021エディションでコンパイルおよび検証されました。「プレビュー」または「スケルトン」とラベル付けされたスニペット(async、feasible、neighbors)は、完全な実行可能プログラムではなく、入力するテンプレートです。