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バイトユニコード) |
警告: 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) | 2つの値のうち大きい方 |
a.min(b) | 2つの値のうち小さい方 |
std::cmp::min(a, b) | 同じ、フリー関数形式 |
a.saturating_sub(b) | アンダーフローパニックなしの減算 |
警告: アンダーフローがパニックします!
0usize - 1はクラッシュします。while right > leftでループを保護するか、checked_sub/saturating_subを使用します。
3. 文字列とchar
String = 所有可能/成長可能 · &str = 借用スライス。文字列を位置でインデックスすることはできません — charを通じて進めます。
// LeetCodeの標準:Vec<char> に変換
let v: Vec<char> = s.chars().collect();
let n = s.len(); // バイト数!
let n = s.chars().count(); // 実際の文字数
s.chars().nth(i) // Option<char>, O(n)
v[i] // Vec では O(1)
let mut out = String::new();
out.push('a');
out.push_str("bc");
out.trim_end().to_string()
分割と構築
s.split_whitespace() // 単語
s.split(',')
v.iter().collect::<String>() // char → String
"x".repeat(3) // "xxx"
char テストと数学
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個のゼロ
// 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(); // 昇順
v.sort_by(|a, b| b.cmp(a)); // 降順
v.sort_by_key(|x| x.0); // フィールド別
v.sort_unstable(); // より高速、安定性の保証なし
v.dedup(); // 隣接する重複を削除
警告: 比較器で借用を使用します:
a.cmp(&b)、a.0.cmp(&b.0)— クロージャは参照を渡します。
5. イテレータ(Rustで大きな役割)
アダプターをチェーンして、.collect() または値に折り込みます。消費されるまで遅延評価です。
v.iter() // &T (読み取り)
v.iter_mut() // &mut T (インプレース編集)
v.into_iter() // T (消費 / 移動)
| アダプター | 意味 |
.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();
頻度カウント(定番)
// イディオム A — or_insert
*m.entry(c).or_insert(0) += 1;
// イディオム 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) { /* 既に存在していた — 重複! */ }
seen.contains(&x);
ヒント: 編集時の反復処理:
get_mutで最初に借用するか、キーを収集してから変更します — イテレータを保持しながら同時にマップを変更することはできません。
7. BinaryHeap(優先度キュー)
BinaryHeap はデフォルトで 大頂ヒープ です。小頂ヒープの場合、Reverse で値をラップします。
use std::collections::BinaryHeap;
use std::cmp::Reverse;
// 大頂ヒープ
let mut h: BinaryHeap<i32> = piles.into_iter().collect();
h.push(5);
while let Some(top) = h.pop() { /* ... */ }
// 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 は式
let sign = if x >= 0 { 1 } else { -1 };
for i in 0..n { } // 0..n-1
for i in 0..=n { } // 包含的
for i in (0..n).rev() { } // 逆順
for (i, x) in v.iter().enumerate() { }
while left < right { }
loop { break; } // 無限ループ
match — パターンの力
match x {
0 => "zero",
1 | 2 => "small",
3..=9 => "mid",
_ => "big", // キャッチオール必須
}
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では、値が存在することが証明されたら(例:
len() > 0チェック後).unwrap()は通常問題ありません。
11. 所有権と借用
あなたと戦う部分。各値には1つの所有者がいます;移動するか、参照を借用します。
| 形式 | 意味 |
&x | 共有借用(読み取り、複数許可) |
&mut x | 排他的借用(一度に1つ) |
x.clone() | 深いコピー — 借用チェッカーが問題を起こしたときの脱出路 |
// 参照で渡す → 呼び出し元は所有権を保持
fn sum(v: &Vec<i32>) -> i32 {
v.iter().sum()
}
sum(&nums); // nums は後で使用可能
警告: よくあるエラー:
- "ムーブ後の借用" → 移動の代わりに
&を取るか、.clone()。 - "可変として借用できない" → 他の借用がまだ活きています;スコープを短くします。
- 2ポインタスワップが必要? Vec をインデックスで参照を保持する代わりに。
// temp でスワップ
let tmp = a.clone();
a = b;
b = tmp;
// または単純に:
std::mem::swap(&mut a, &mut b);
12. 関数とクロージャ
fn add(a: i32, b: i32) -> i32 {
a + b // セミコロンなし = 戻る
}
fn noop() { } // () を返す
// ネストされたfn(キャプチャなし) — ソリューションで便利
fn helper(v: &Vec<i32>) -> i32 { /* .. */ }
// クロージャは環境をキャプチャできます
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ポインタとスライディングウィンドウ
// 両端からの2ポインタ(パリンドローム / ペア和)
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; }
}
// 動く開始を持つスライディングウィンドウ
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が0になる可能性があるため、right -= 1をusizeアンダーフローから保護します。while left < right条件は通常あなたを救います。
14. パターン:2D DPテーブル
// 例えば 最長共通部分列 — 右下から埋める
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])
};
}
}
// 答えは 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() |
| mut + immut 借用 | スコープを分割;参照ではなくインデックスを取得 |
| String をインデックスできない | 最初に .chars().collect::<Vec<_>>() を実行 |
| 合計には型が必要 | let s: i32 = it.sum(); |
| 整数オーバーフロー | 合計が大きくなったら i64 を使用 |
| 浮動小数点の床 | f64 にキャスト、.floor()、戻してキャスト |
デバッグプリント
println!("{}", x); // Display
println!("{:?}", v); // Debug (Vec, tuple)
println!("{:#?}", m); // きれいな Debug
eprintln!("err {}", e); // stderr
17. 構造体を選ぶ
| 必要なもの | 選択する |
| 順序付きリスト、インデックス可能 | Vec<T> |
| キーでカウント / ルックアップ | HashMap |
| 以前見たもの / ユニーク | HashSet |
| 常にmax/min をポップ | 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
}
最左 / 下限
// 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 が挿入ポイント
ヒント: 標準ショートカット:
v.binary_search(&t)→Ok(i)見つかった場合、Err(i)= 挿入場所。またpartition_point(|&x| x < t)。
2. 答えの二分探索
質問が「feasible(X)」となるような最小X」の場合 — 配列ではなく、答えのスペースを検索します。
let (mut lo, mut hi) = (min_ans, max_ans);
while lo < hi {
let mid = lo + (hi - lo) / 2;
if feasible(mid) {
hi = mid; // より小さいものを試す
} else {
lo = mid + 1; // より大きいものが必要
}
}
// lo == 最小の実現可能な答え
fn feasible(x: i64) -> bool {
// O(n) の貪欲チェック
true
}
ヒント: クラシックな使用法:Kokoがバナナを食べる、配列を分割する最大和、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() {
// 1つのレベル全体を処理
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回の呼び出しでチェックとマークの両方を行います。キューにダブルを避けるために、dequeue ではなく enqueue でマークします。
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;
// usize にキャストバックする前に境界チェック
if nr >= 0 && nr < rows as i32
&& nc >= 0 && nc < cols as i32 {
let (nr, nc) = (nr as usize, nc as usize);
// 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);
}
}
}
// 呼び出し:
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()); // 記録
for i in start..nums.len() {
path.push(nums[i]); // 選択
backtrack(i + 1, nums, path, out); // 探索
path.pop(); // 選択解除
}
}
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; // 圧縮
}
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; } // 既に結合されている
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. プリフィックス合計
1回プリコンピュート → 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;
}
// nums[l..=r] の合計(包含的)
let range = pre[r + 1] - pre[l];
部分配列の合計 == k(hashmap トリック)
use std::collections::HashMap;
let mut seen: HashMap<i64, i32> = HashMap::new();
seen.insert(0, 1); // 空プリフィックス
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]
}
// 初期化:vec![-1; n]
ヒント: 2D 状態 →
vec![vec![-1; m]; n]。HashMap キー状態の場合HashMap<(usize, usize), i64>を使用します。
10. 単調スタック
「次のより大きい/より小さい要素」を O(n) で。スタックは インデックス を保持します;新しい値が順序を破る場合はポップします。
// 右の次の最大要素
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 は現在中点
貪欲な間隔スケジューリング
// 終了でソート、非重複を取得
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 を検索 | 二分探索 |
| 「min/max … そのような」 | 答えに対する BS |
| 最短経路、重み付けなし | BFS |
| すべてのパス / コンボ / パーム | バックトラッキング |
| 接続グループ / サイクル | union-find |
| 範囲合計を繰り返す | プリフィックス合計 |
| 次のより大きい/より小さい | 単調スタック |
| 重複するサブ問題 | DP(メモ/テーブル) |
| 隣接ウィンドウ | スライディングウィンドウ |
| トップk / ストリーミング max | BinaryHeap |
Big-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は結果をすべてスレッド化して?で伝播します。
1. 構造体とメソッド
struct User {
id: u64,
name: String,
active: bool,
}
impl User {
// 関連fn(コンストラクタ慣習)
fn new(id: u64, name: String) -> Self {
Self { id, name, active: true }
}
// &self = 借用(読み取り)
fn label(&self) -> String {
format!("#{}: {}", self.id, self.name)
}
// &mut self = 借用(変異)
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のenumはデータを運びます。これは「複数の形状の1つ」をモデル化する方法です — ドメインモデリングの主力。
enum Event {
Click { x: i32, y: i32 }, // struct のような
Key(char), // tuple のような
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()?; // ? = アンラップまたは Err を返す
let y: i32 = b.parse()?;
Ok(x + y)
}
// 境界で処理
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;
// デフォルトメソッド(オプションのオーバーライド)
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());
Derive — 無料の一般的な特性
// 手書きの代わりに自動実装
#[derive(Debug, Clone, PartialEq)]
struct Point { x: i32, y: i32 }
// これで:{:?} プリント、.clone()、==
| 派生 | 有効にする |
Debug | {:?} プリント |
Clone | .clone() |
PartialEq | == |
Default | ::default() |
5. ジェネリクスと特性境界
コードを1回書く多くの型の場合。境界は「これらの特性を実装する任意の型 T」と言います。
// T は比較可能(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」句
fn show<T>(item: T)
where T: std::fmt::Debug {
println!("{:?}", item);
}
// impl Trait — 「それは ...」という種
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 所有者、深いコピーなし
println!("{}", Rc::strong_count(&a)); // 2
// 再帰型は既知のサイズのために Box が必要
enum List { Cons(i32, Box<List>), Nil }
警告: 実際に使用するコンボ: 1つのスレッドで共有可能な可変状態の場合
Rc<RefCell<T>>;スレッド全体で同じ場合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() → ガード;ドロップするとアンロック
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()は「 unwrap を止める」への従来の例外です —.lock()はロックを保持している別のスレッドがパニックした場合にのみエラーが発生します(「中毒化」mutex)。非同期バックエンドでは、多くの場合、代わりにtokio::sync::Mutexを使用するため、ロック は.await全体で保持できます。
8. Send + Sync(理由)
2つのマーカー特性。コンパイラは並行性安全性を確保するために使用します。あなたはめったにそれらを書きません — あなたはただそれらを満たします。
| 特性 | 意味 |
Send | 別のスレッドへ 移動 しても安全 |
Sync | スレッド全体で 共有 (&T)しても安全 |
これは Rc がスレッド全体でコンパイルされない理由ですが、Arc はそうします:Rc は Send ではありません。コンパイラはデータレースが存在する前にそれをキャッチします — エラーメッセージは、欠けている正確にどの境界を伝えます。
警告: これを暗記しないでください。 ただ知る:コンパイラが「
Xはスレッド間で安全に送信できない」と言う場合、通常はRc/RefCellの代わりにArc/Mutexが必要です。
9. モジュール、クレート、Cargo
# プロジェクトを開始 / 依存関係を追加
cargo new my_api
cargo add serde --features derive
cargo add tokio --features full
// モジュールはコードを整理
mod db {
pub fn connect() { /* */ }
pub struct Pool;
}
use db::Pool; // スコープに取り込む
db::connect(); // または完全に適格
| キーワード | 意味 |
pub | モジュール外で項目を表示可能にする |
mod x; | x.rs / x/mod.rs を読み込む |
crate:: | クレートルートからのパス |
super:: | 親モジュールからのパス |
10. async / await(プレビュー)
バックエンドフレームワークは非同期ファースト。async fn は、ランタイム(Tokio)によって .await されるまで何もしない future を返します。
async fn fetch(id: u64) -> Result<String, String> {
// ... DB / ネットワーク呼び出しを待つ
Ok(format!("user {}", id))
}
#[tokio::main]
async fn main() {
let user = fetch(1).await.unwrap();
println!("{}", user);
}
-
.awaitは待機中に制御を生成します — スレッドをブロックしません。 -
#[tokio::main]は futureを駆動するランタイムを設定します。 -
一般的なスタック:axum(web)+ sqlx(DB)+ serde(JSON)。
11. ここからの推奨順序
| ステップ | 焦点 |
| 1 | 小さなドメインをモデル化して構造体、enum、match を固める |
| 2 | .unwrap() 習慣を Result + ? で変換どこでも |
| 3 | 特性 + ジェネリクス;一般的なものを derive |
| 4 | スマートポインタ;Rc<RefCell> で何かを構築 |
| 5 | Arc<Mutex> + thread::spawn 共有状態 |
| 6 | async / await + Tokio の基本 |
| 7 | axum で小さなJSON APIを構築 |
無料、高品質なリソース
-
The Book — doc.rust-lang.org/book(10、13、15–16章がこの部分にマップします)
-
Rust by Example — 実行可能なスニペット
-
Tokio チュートリアル — 非同期にヒットしたとき
このリファレンス内のすべてのコードはRust 2021版でコンパイルされ、検証されました。「プレビュー」または「スケルトン」(async、feasible、neighbors)とラベル付けされたスニペットは、完全に実行可能なプログラムではなく、入力するテンプレートです。