メインコンテンツまでスキップ
最新2mo ago

Rust構文の基礎

3部構成のリファレンス:問題解決のための言語コア、アルゴリズムパターンテンプレート、パズルから実際のバックエンド開発への橋渡け。ここにあるすべてのコードはコンパイルされ、検証されています。

目次

  1. 構文とデータ構造

  2. アルゴリズムパターンとテンプレート

  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 / chartrue · '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);
}

警告: right0 になる可能性があるため、right -= 1usize アンダーフローから保護します。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、戻る = i32as でキャスト
ムーブされた値& で借用するか、.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 キュー / BFSVecDeque
LIFO / マッチングVec をスタックとして
固定グリッド / DPvec![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
}
}

ヒント: unionfalse を返すことはサイクルを意味します(両方のエンドポイントが既に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 / ストリーミング maxBinaryHeap

Big-O サニティ(n ≤ …)

n ≤負担可能
10–12O(n!) / O(2ⁿ) バックトラッキングok
5,000O(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 => { /* */ }
}
}

ヒント: OptionResult は標準の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()ResultOption(エラーをドロップ)
.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 はそうします:RcSend ではありません。コンパイラはデータレースが存在する前にそれをキャッチします — エラーメッセージは、欠けている正確にどの境界を伝えます。

警告: これを暗記しないでください。 ただ知る:コンパイラが「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> で何かを構築
5Arc<Mutex> + thread::spawn 共有状態
6async / await + Tokio の基本
7axum で小さなJSON APIを構築

無料、高品質なリソース

  • The Book — doc.rust-lang.org/book(10、13、15–16章がこの部分にマップします)

  • Rust by Example — 実行可能なスニペット

  • Tokio チュートリアル — 非同期にヒットしたとき


このリファレンス内のすべてのコードはRust 2021版でコンパイルされ、検証されました。「プレビュー」または「スケルトン」(async、feasibleneighbors)とラベル付けされたスニペットは、完全に実行可能なプログラムではなく、入力するテンプレートです。