はじめに
Rustを学び始めるとき、AtCoder Beginners Selection(以下ABS)を最初の演習場に選ぶ人は多い。 1問1ファイルで完結し、提出してジャッジが通れば「動く」が確定する、という気軽さがある。
ただ、Rust完全初学者がABSにいきなり突っ込むと、典型的な詰みパターンを踏みやすい。
unwrap()を脳死で連打して、Resultの意味を理解しないまま通してしまう- 所有権・借用が面倒なところは
clone()で逃げてしまう - マクロや競プロ用テンプレを早く取り込みすぎて、標準ライブラリの薄さに気付けない
通すこと自体はできるが、得られたものは「Rustを書いた」ではなく「Rustっぽい何かを書いた」になる。 言語仕様への理解が積み上がらないので、5問目あたりで急にコンパイラに殴られて止まる。
この記事は、自分が完全初学者の状態でABSに着手するときに、Claudeを教師役として組み込んだ6ステップの学習フローをまとめたものだ。 PracticeA 1問目の実走でこのフローを試し、AC(提出 #75356423、100/100、1ms)まで到達できたので、再現可能な形で残しておく。
設計方針
最初に、フロー設計のときに置いた3つの方針を書いておく。
| 方針 | 理由 |
|---|---|
| トピック単位で深掘りしてから書く | アンチパターン化を防ぐ。動かすことを優先しない |
| Claudeに解答コードを書かせない | 自分の言葉で書けない部分が浮かばなくなる |
| 公式本の章番号と紐付ける | 学習が「文法 → リファレンス参照」の経路で繋がるようにする |
unwrap() 乱用や clone() 逃げは、コードを動かすことを最優先にした瞬間に発生する。
最初の数問だけは、AC すること以上に「なぜそう書くか」が言える状態を維持することを優先したい。
フロー全体像
1問あたりのフローは6ステップに分けている。
Step 1: 自分 問題文を確認
↓
Step 2: 自分 問題文と入出力例をClaudeへ共有
↓
Step 3: Claude 必要なトピックを最小限で解説
↓
Step 4: 自分 自力でRustコードを書く
↓
Step 5: 自分 AtCoderに提出(ギブアップでもAC でも)
↓
Step 6: Claude 自分の回答をレビュー
肝は Step 3 と Step 6 の役割分担だ。 Step 3 は「文法と概念の地図」を提供するだけ、Step 6 は「書いたコードのイディオム健全性」をチェックするだけ。 解答そのものはどちらでもClaudeに書かせない。
Step 3 で Claude が守ること
教師役のClaudeは、その問題で必要な最小限のトピックに絞って解説する。 「ついでに知っておくと良いこと」は徹底的に削る。
実運用で守ってもらっているルールはこんな感じだ。
- その問題で必要な最小限のトピックに絞る(先取りしすぎない)
- 該当する公式本(The Rust Programming Language)の章番号・節を必ず紐付ける
- 例: 所有権 → ch4、
Vec→ ch8、io::stdin→ ch2
- 例: 所有権 → ch4、
- Rust標準の入力処理イディオムを示す(
read_line+trim+parseの基本形) - 競プロ的ショートカット(マクロ濫用、
proconioのような外部crateの先取り)には流れない - 解答コードは書かない。概念とAPIの説明、ヒント/シグネチャ程度に留める
- 詰まった時は段階的にヒント(問いかけ → API例 → 部分コード)
「解答コードは書かない」を明示しておくのが効く。 書かせた瞬間、自分の脳内で組み立てる工程がスキップされて、書いた気になってしまう。
Step 4 で自分がやること
Claudeが地図を渡してくれるので、あとは自力で実装する。
PracticeA であれば、地図はこのくらいの解像度で渡される。
- 標準入力は
io::stdin().read_line(&mut buf)で1行ずつ - 末尾の
\nをtrim()で落とす - 文字列から数値は
parse::<i32>()または型注釈つきlet x: i32 = "42".parse().unwrap() - 複数値を分けるには
split_whitespace().collect::<Vec<&str>>() - 出力は
println!("{} {}", x, y)
これだけ渡されれば、初学者でも組み立てられる。 書けない部分が出てきたら、その時点で詰まったトピックをClaudeに掘り下げてもらう。 これも「概念の問いかけ」までで止めて、コードは自分で書く。
Step 5: 提出する(ギブアップでも良い)
書けたらAtCoderに提出する。 通らなくても良いし、書ききれずにギブアップしても良い。
「とりあえず通ったコード」を持って Step 6 に進めば、Claudeはそれを起点にレビューしてくれる。 通っていなくても、書きかけのコードと詰まった箇所を渡せば、Step 6 はそのまま機能する。
ここで重要なのは、提出を恥ずかしがって遅らせないことだ。 ジャッジが通ったか落ちたかは、自分のコードを客観評価する一番安いシグナルなので、書けた時点で投げる。
Step 6 で Claude が見るポイント
レビュー時にClaudeが見るのは、動いているかどうかではなく、コードがイディオムとして自然かどうかだ。
具体的にチェックしてもらっている観点は4つ。
- 動いているか以前にイディオムとして自然か
unwrap()/clone()の使い所が妥当か(避ける所と許容する所の判別)- 所有権・借用が型システムと噛み合っているか
- 公式本のどの章を読むと深まるか(次に進むべき章の提示)
「次に進むべき章」を毎回もらえると、書く → 学ぶ → 書く のサイクルがそのまま積み上がる。
PracticeA の後だと、? 演算子(ch9)と入力処理ヘルパーの抽出(ch7 のモジュール)が次の候補として出てきた。
実走してみてどうだったか
PracticeA でこのフローを通した結果、得られたものは大きく3つあった。
expect("...")をunwrap()ではなく書いた- 学習段階では
expect("メッセージ")のほうが、失敗時に何が起きたか分かるので推奨されると Step 3 で説明された - 「とりあえず動かす」目的なら
unwrap()でも通るが、その差を意識して書けたのは大きい
- 学習段階では
Stringと&strの関係が手で動かして納得できたread_lineは&mut Stringを要求し、trim()の戻り値は&strだった- 「所有する文字列」と「借りているスライス」が同じコードに混ざる体験を、最小コードで踏めた
- 次に学ぶ章のリストが手元に残った
?演算子(ch9)、next()を使ったメモリ確保ゼロのパース、src/lib.rsへの入力ヘルパー抽出- 次の問題に着手するときに「何を勉強しながら解くか」が決まっている状態で始められる
逆に、改善したい点もあった。
expect("...") のメッセージが、read_line 側はプレースホルダーのまま、parse 側だけ具体メッセージで書いてしまった。
こういう「動くけど揃っていない」部分は、Step 6 で初めて気付ける。
自分で気付けないので、レビューを必ず通すのが効く。
このフローを使わないケース
ABSの後半(5問目以降)や、すでにRustに慣れた状態では、このフローはオーバースペックになる。 特に Step 3(必要トピックの解説)は、自分で公式本を引けるようになった時点で省略していい。
逆に、別言語(PythonやGo)から来た中級者がRustを始めるときは、このフローのうち Step 6(レビュー)だけは残しておくと効く。 所有権・借用は、他言語の経験があっても初手では正しく書けないことが多く、第三者の視点でイディオムを点検してもらうメリットが大きい。
おわりに
「Claudeを教師役にする」と書くと、聞こえは新しいが、本質は普通の家庭教師の使い方と変わらない。 解答を書かせない、進度を超えて先取りさせない、書いたものをレビューしてもらう、という昔ながらの構造を、AIに移したというだけだ。
差があるとすれば、24時間付き合ってくれる教師役を、月額数千円程度で雇えてしまうという点だ。 その安さに引きずられて、つい「答えそのものを聞く」に流れる。 そこを意識して堰き止めるための、6ステップのプロセス、というのがこの記事の中身になる。
ABSの全11問を、このフローで通せるかは正直分からない。
途中で軽量化するかもしれないし、Step 3 を proconio 解禁に切り替える日も来るかもしれない。
ただ、最初の何問かは、この重さで通すことに価値がある、というのが PracticeA 1問の実走から得た結論だ。
関連
変更履歴
- 2026-05-05: 初版公開