はじめに

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
  • Rust標準の入力処理イディオムを示す(read_line + trim + parse の基本形)
  • 競プロ的ショートカット(マクロ濫用、proconio のような外部crateの先取り)には流れない
  • 解答コードは書かない。概念とAPIの説明、ヒント/シグネチャ程度に留める
  • 詰まった時は段階的にヒント(問いかけ → API例 → 部分コード)

「解答コードは書かない」を明示しておくのが効く。 書かせた瞬間、自分の脳内で組み立てる工程がスキップされて、書いた気になってしまう。

Step 4 で自分がやること

Claudeが地図を渡してくれるので、あとは自力で実装する。

PracticeA であれば、地図はこのくらいの解像度で渡される。

  • 標準入力は io::stdin().read_line(&mut buf) で1行ずつ
  • 末尾の \ntrim() で落とす
  • 文字列から数値は 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つあった。

  1. expect("...")unwrap() ではなく書いた
    • 学習段階では expect("メッセージ") のほうが、失敗時に何が起きたか分かるので推奨されると Step 3 で説明された
    • 「とりあえず動かす」目的なら unwrap() でも通るが、その差を意識して書けたのは大きい
  2. String&str の関係が手で動かして納得できた
    • read_line&mut String を要求し、trim() の戻り値は &str だった
    • 「所有する文字列」と「借りているスライス」が同じコードに混ざる体験を、最小コードで踏めた
  3. 次に学ぶ章のリストが手元に残った
    • ? 演算子(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: 初版公開