メインコンテンツまでスキップ
最新1mo 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バイトのunicode)

警告: LeetCodeの第1の落とし穴: インデックスは usize だが答えは通常 i32as で自由にキャストできます:(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 -= 1right0 の場合の 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、戻り値 = i32as でキャスト
移動された値& を借用するか .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キュー / 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
}

最左 / 下限

// 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
}
}

ヒント: unionfalse を返すことは、サイクルを意味します(両方のエンドポイントは既に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–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は 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 => { /* */ }
}
}

ヒント: 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()?; // ? = 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()ResultOption(エラーをドロップ)
.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 はする理由です:RcSend ではありません。コンパイラはデータ競合をそれらが存在する前に検出します — エラーメッセージはどのバウンドが不足しているかを正確に伝えます。

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

無料の高品質リソース

  • — doc.rust-lang.org/book(章10、13、15–16がこのパートにマップ)

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

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


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