
はじめに
皆さん、こんにちは。
技術開発部のWです。 普段はWebエンジニアとしてフロントエンドやサーバーサイドの開発に携わっています。最近 Rust を学び始めました。
Clean Architecture の復習を兼ねて、学習中の Rust で最小の TODO アプリを作ってみることにしました。せっかくなので Claude Code の Learning Style という機能を使い、自分でコードを書きながら進める形式で試してみました。
この記事では、その体験の全記録をスクリーンショット付きで紹介します。
想定読者は以下のような方です。
- Clean Architecture を知っているが、Rust で実装したことはない
- Claude Code を開発学習に活用してみたい
- AI ツールに「答えを出させる」のではなく「学びを支援させる」使い方に興味がある
目次
- Claude Code の Learning Style とは
- 使ったプロンプト
- 実践:5ステップの学習体験
- Learning Style の3つの特徴
- やってみた感想
- 最終的なディレクトリ構成
- まとめ
Claude Code の Learning Style とは
Claude Code には output-style という設定があり、応答のスタイルを切り替えられます。
/output-style コマンドで Learning を選択すると、Claude Code が以下のように振る舞います。
- コードを全部書いてくれるのではなく、学習者にコードを書かせる
- 設計判断が必要な箇所で「Learn by Doing」として課題を出す
- 実装後にレビューと Insight(教育的な解説)を提供する
つまり、答えを教えるのではなく、考えさせてからフィードバックをくれるメンターに近い使い方ができます。
使ったプロンプト
Learning Style を有効にした上で、以下のプロンプトを入力しました。
RustでClean Architectureの基本を学ぶためのtodoアプリ用README.mdを作成してください。 - 想定ディレクトリ: clean-architecture-rust/ - 目次を必ず含める - 全体の想定学習時間は合計15分以内とする - README内にコード例は一切記載しない - Claude Codeによるコード生成・ファイル作成も禁止 - 全体100行以内でまとめる - 各ステップに学習者への問いかけを含め、自分で考えてから手を動かす構成にする - 学習者自身がコードを書く前提で、各ステップには実装の指針(何を・どこに)とヒントだけ示す
このプロンプトで特に工夫したポイントは3つです。
- 「コード例は一切記載しない」「学習者自身がコードを書く前提」 ── Claude Code にコードを書かせるのではなく、学習のロードマップだけを作らせて、実装は自分でやるという方針
- 「全体の想定学習時間は合計15分以内」 ── これは実際の学習時間の見積もりではなく、README の出力ボリュームを抑えるための制約です。「15分で終わる内容量」と指定することで、100行以内に収まる粒度にコントロールしています
- 「全体100行以内」 ── 上と合わせて、各ステップを「問いかけ + 実装指針 + ヒント」の3要素に絞らせるための制約

プロンプトを入力すると、Claude Code はいきなり作業に入るのではなく、3つの確認質問をしてきました。
- 学習者のレベル: Rust 初学者も含むか
- 永続化層: インメモリ(Vec)かファイル保存か
- 操作範囲: 追加・一覧表示のみか、CRUD 全部か
15分という制約の中で何にフォーカスするか、Claude Codeからスコープの確認を求められています。ここでは「Rust 初学者も含む」「インメモリのみ」「追加・一覧表示のみ」を選択しました。
実践:5ステップの学習体験
Claude Code が作成した README は100行ぴったり。5ステップ × 各3分の構成です。

ここから実際の実装に入ります。
Step 1:ドメイン層 ── Todo struct を定義する
Claude Code はプロジェクトの初期化(cargo init)とディレクトリ構造、mod.rs などの定型コードを準備した上で、src/domain/todo.rs にこんなプレースホルダーを置きました。
// TODO(human): ここに Todo struct を定義してください
そして「Learn by Doing」として課題が提示されます。

Your Task:
src/domain/todo.rsの TODO(human) を置き換えて、Todo struct を定義してください。「追加」と「一覧表示」という2つの操作から逆算して、必要最小限のフィールドを考えてみてください。
ヒントとして「識別子が必要」「#[derive(...)] で Debug と Clone を付けると後が楽」という情報だけが与えられます。答えは教えてくれません。
実際にエディタで書いてみました。

書き終えて「確認してください」と伝えると、Claude Code がレビューしてくれます。
最初のレビューでは、まず completed フィールドについてコメントがありました。今回のスコープは「追加・一覧表示」だけなので completed は不要です。ただ、TODO アプリなら完了状態は持っていて当然だろうと思い、あえて入れていました。
Claude Code の反応は「今回のスコープでは直接使わないが、TODO として自然な属性なので問題ない」。スコープ外だから削除しろ、ではなく設計意図を汲んでくれたのは少し意外でした。
その上で2点の修正指摘がありました。

- 可視性(
pub)が未指定 ── struct 自体とフィールドの両方にpubが必要 #[derive(...)]が未指定 ──Debugがあるとprintln!で表示でき、Cloneは後のステップで Vec から取り出すときに役立つ
この2点を修正して再度確認を依頼すると、さらにフィールドレベルの pub が足りないという指摘がありました。

pub struct にしてもフィールドはデフォルト非公開で、他の層から todo.title と書いてもコンパイルエラーになるという指摘です。Rust の可視性は struct とフィールドが独立しているという設計思想も教えてくれました。
このように段階的にレビューを受けながら修正し、最終的に以下の形で Step 1 が完了しました。
#[derive(Debug, Clone)] pub struct Todo { pub id: u32, pub title: String, pub completed: bool, }
ステップの順序変更 ── コンパイル駆動開発
Step 1 が終わると、Claude Code は README の Step 2(ユースケース層)ではなく、Step 3(リポジトリ境界)を先にやろうと提案してきました。

理由は、ユースケース層がリポジトリの trait に依存するため、trait を先に作れば cargo check が通る状態を保てるから。
Claude Code はこれを「コンパイル駆動開発」と呼んでいました。各ステップでコンパイルを通しながら進めるアプローチで、Rust の型システムと相性が良い進め方です。
Step 3 → Step 2:リポジトリ境界とユースケース層
リポジトリの trait 定義では、ヒントでは Vec<Todo> を直接返す案が示されていましたが、自分の判断で Result<T, String> を戻り値に使いました。Rust では失敗しうる操作に Result を使うのが慣習であり、リポジトリのようなデータ操作は本来失敗する可能性があるものだと考えたからです。

pub trait TodoRepository { fn save(&mut self, todo: Todo) -> Result<Todo, String>; fn get_all(&self) -> Result<Vec<Todo>, String>; }
これに対して Claude Code は「実践的な判断」と評価してくれました。

ヒントを超えた判断にもフィードバックが返ります。さらに Insight として「実務では enum TodoError のような独自エラー型を定義するのが一般的」という発展的な情報も教えてくれました。
続いてユースケース層です。ここでは TodoUsecase<R: TodoRepository> という書き方をします。<R: TodoRepository> はジェネリクス(型パラメータ)を使った書き方で、「TodoRepository trait を満たす任意の型 R を受け取れる」という意味です。この仕組みにより、ユースケース層は具体的な保存方法を知らないまま動作できます。

ここで Claude Code が指摘した重要な点があります。

このファイルの
use文を見てください。crate::domain::todoとcrate::repository::todo_repositoryだけをインポートしています。crate::infrastructureは一切登場しません。これが Clean Architecture の依存性逆転が正しく機能している証拠です。
use 文から依存関係を読み取るという視点は、自分だけでは気づきにくいポイントでした。
Step 4:所有権エラーとの出会い
インフラ層(InMemoryTodoRepository)の実装で、Rust ならではの学びがありました。

Claude Code からは「Vec<Todo> をフィールドに持たせる」「save では push した後に保存した Todo を返す(Clone が役立つ場面)」というヒントが出されました。
このヒントを頼りにエディタで実装してみました。

最初に書いたコードはこうです。
fn save(&mut self, todo: Todo) -> Result<Todo, String> { self.todos.push(todo); // ← ここで todo の所有権が Vec に移動 Ok(todo) // ← 移動済みの todo を使おうとしてエラー }
Claude Code はすぐにこの問題を指摘しました。

Rust の所有権(ownership)システムが防いでくれる典型的なバグです。C/C++ では解放済みメモリへのアクセスとして実行時に問題になりますが、Rust ではコンパイル時に検出されます。
指摘されて「あ、これか」と。Step 1 で #[derive(Clone)] を付けたのがここで活きました。clone() で複製を作ってから push する修正で解決です。

fn save(&mut self, todo: Todo) -> Result<Todo, String> { let saved = todo.clone(); // 先に複製を確保 self.todos.push(todo); // オリジナルを Vec に移動 Ok(saved) // 複製を返却 }
Step 5:main.rs で組み立てる ── Composition Root
最後のステップでは、全層を main.rs で組み立てます。Claude Code はここを「Composition Root(合成ルート)」と呼んでいました。DI(依存性注入)を行う場所、つまり「どの具体型を使うか」を決定する唯一のファイルです。

use crate::infrastructure::in_memory_repository::InMemoryTodoRepository; use crate::usecase::todo_usecase::TodoUsecase; fn main() { let repository = InMemoryTodoRepository::new(); let mut usecase = TodoUsecase::new(repository); usecase.add_todo("Buy groceries".to_string()).unwrap(); usecase.add_todo("Walk the dog".to_string()).unwrap(); usecase.add_todo("Read a book".to_string()).unwrap(); let todos = usecase.get_all_todos().unwrap(); println!("TODO List:"); for todo in todos { println!("- {}", todo.title); } }
cargo run を実行すると、無事にビルドが通り、動作しました。

TODO List: - Buy groceries - Walk the dog - Read a book
振り返り:依存の方向を図で理解する
全ステップ完了後、Claude Code から3つの振り返り問題が出されました。
- 保存先を Vec からファイルに変える場合、どの層のどのファイルだけを変更すれば済むか?
- ユースケース層のコードは保存方法の変更に影響を受けるか?
- 自分の実装で「依存の方向は常に外→内」のルールは守れているか?
私の回答は以下の通りです。
- infraの実装を主に変更して、mainでインスタンスを生成
- 保存方法はユースケースは知らないため影響はない
- 常に外から内になっている、内: domain ← repository(domainだけuse::crate) ← usecase(domainとrepositoryをuse::crate) ← infra(domainとrepositoryをuse::crate) ← main :外
これらに答えると、Claude Code が同心円の図を出してくれました。

そして依存性逆転のポイントも図示されました。

この図の核心は、usecase と infrastructure が直接繋がっておらず、repository trait を介して間接的に繋がっている点です。矢印が内側に向かって合流する構造が、依存性逆転の原則(DIP)そのものです。
Learning Style の3つの特徴
ここからは実践を振り返って、Claude Code Learning Style の特徴を3つにまとめます。
1. TODO(human) パターン
Claude Code は定型コード(mod.rs、use 文)だけを用意し、本質的な設計判断が必要な部分に TODO(human) を置きます。
さらに、各ファイルの冒頭には // ドメイン層: TODO エンティティ // この層は他のどの層にも依存しない のようなコメントが自動で記載されます。これも Claude Code がヒントとして付けてくれたもので、エディタでコードを書いている最中に「今自分がどの層を書いていて、どんな制約があるか」を確認できます。
「何を考えるべきか」が明確になり、自分で書くぶん記憶にも残ります。
2. レビュー → 修正のサイクル
書いたコードを「確認してください」と伝えると、レビューが返ってきます。
今回の体験では、Step 1 だけで2回のレビューサイクルがありました。1回目で pub と #[derive] の未指定を指摘され、修正後の2回目でフィールドの pub 漏れを追加指摘される、という流れです。段階的にコードが改善されていく過程は、実際のコードレビューに近い感覚でした。
3. Insight による文脈の補足
各ステップの前後で「Insight」と題した教育的な解説が挿入されます。
特に印象的だったのは以下の3つです。
use文から依存関係を読み取れるという指摘(Step 2)- 所有権エラーを
#[derive(Clone)]が解決するという Step 1 との伏線回収(Step 4) main.rs= Composition Root という Clean Architecture の用語との対応付け(Step 5)
単にコードの正誤を判定するのではなく、「なぜそうなるのか」「他のどこと繋がっているのか」を補足してくれる点が、ドキュメントを読むだけの学習との大きな違いでした。
やってみた感想
学習時間について
前述の通りプロンプトの15分はボリューム調整用の制約で、実際の所要時間はレビューのやりとりを含めて 1時間弱 でした。写経では得られない理解が伴っているので、学習の密度としては十分です。
Claude Code Learning Style が向いている場面
- 新しい言語 × 新しい設計パターンの組み合わせ。今回の「Rust × Clean Architecture」のように、両方を同時に学びたいときに特に効果的でした
- 一人で学習していて、レビューしてくれる人がいないとき。段階的なフィードバックが得られるので、間違った理解のまま進むリスクが減ります
注意点
- Learning Style は考えさせてくれる反面、効率重視の作業には向きません。既知の技術でサッと実装したい場合は通常モードの方が適切です
- プロンプト設計が体験の質を左右します。今回のように「コード例は載せない」「学習者自身がコードを書く」という制約を入れることで、学習メンターとしてうまく機能しました
- 同じプロンプトでも、毎回同じ出力になるとは限りません。ステップの順序変更(今回の「コンパイル駆動開発」による Step 3 先行)や Insight の内容は、実行のたびに変わる可能性があります。この記事はあくまで一回の体験記録です
Clean Architecture × Rust の学び
Rust の型システム(trait、ジェネリクス、所有権)が Clean Architecture の原則を自然に強制してくれる、というのが今回の実感です。
- trait が依存性逆転の原則(DIP)を実現する
- ジェネリクスがユースケース層を具体実装から切り離す
- 所有権システムがデータの流れを明示的にする
他の言語では規約やレビューで守るべきルールを、Rust ではコンパイラが強制してくれます。
最終的なディレクトリ構成
clean-architecture-rust/
└── src/
├── main.rs # Composition Root
├── domain/
│ ├── mod.rs
│ └── todo.rs # Todo struct(エンティティ)
├── repository/
│ ├── mod.rs
│ └── todo_repository.rs # TodoRepository trait(境界)
├── usecase/
│ ├── mod.rs
│ └── todo_usecase.rs # TodoUsecase(ビジネスロジック)
└── infrastructure/
├── mod.rs
└── in_memory_repository.rs # InMemoryTodoRepository(具体実装)
まとめ
Claude Code の Learning Style を使うことで、「教わる」のではなく「自分で考えて書く」学習体験ができました。
Clean Architecture の概念は書籍やブログで理解できますが、実際に層を分けてコードを書き、use 文の依存方向を自分で確認し、所有権エラーと格闘する過程で、理論が実感に変わりました。
Clean Architecture を別の言語で実装してみたい方、あるいは AI ツールを学習支援に活用してみたい方は、ぜひ Claude Code の Learning Style で試してみてください。
スパイダープラスでは仲間を募集中です。 スパイダープラスにちょっと興味が出てきたなという方がいらっしゃったらお気軽にご連絡ください。









