Claude Media
Claude CodeでAxumのRust APIを開発する — cargo checkとclippyの回し方

Claude CodeでAxumのRust APIを開発する — cargo checkとclippyの回し方

AxumのextractorやHandlerの型エラーをcargo checkの出力でClaude Codeに直させる反復と、cargo系コマンドのpermissions・CLAUDE.md規約の書き方をまとめます。

AxumのWeb APIをClaude Codeで開発するときの勘所は、型エラーを人間が読み解かず、cargo checkの出力をそのまま渡して直させる反復を作ることです。Axumのエラーは長くなりがちですが、原因の候補はドキュメントに列挙されています。その条件を規約として先に渡しておくと、直しの精度が安定します。

この記事では、Axumの基本構造、CLAUDE.mdに書く規約、cargo系コマンドのpermissions、型エラーを直す反復、clippyとテストの回し方を順に扱います。

Axumとは何か — 型で組み立てるRustのWebフレームワーク

Axumは、HTTPのルーティングとリクエスト処理に特化したRustのライブラリです。マクロを使わないAPIでハンドラーにルートを割り当て、extractor(リクエストから必要な部分を取り出す型)で入力を宣言的に解析します。ミドルウェア用の独自機構は持たず、towerのServiceを使うため、タイムアウト・トレース・圧縮・認可といった既存のtower系部品がそのまま使えます。

ハンドラーは、0個以上のextractorを引数に取り、レスポンスに変換できる値を返す非同期関数です。最小の構成は次のとおりです(ドキュメントの「Hello, World!」と同じ形)。

use axum::{routing::get, Router};
 
#[tokio::main]
async fn main() {
    let app = Router::new().route("/", get(|| async { "Hello, World!" }));
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

#[tokio::main]を使うには、tokioのmacrosとrt-multi-threadの各機能(またはfull)が必要です。必要な依存関係はaxum・tokio・towerの3つで、towerは必須ではないもののテストで役立つとされています。

[dependencies]
axum = "<最新版>"
tokio = { version = "<最新版>", features = ["full"] }
tower = "<最新版>"

ローカル検証では、この例の0.0.0.0(全インターフェースで待ち受け)をループバックの127.0.0.1に変えるほうが無難です。この種の既定値の調整も、次節の規約に入れておけば毎回指示せずに済みます。

CLAUDE.mdに書くAxum開発の規約

CLAUDE.mdは強制される設定ではなく、Claudeが読む文脈です。Claude Codeの公式ドキュメントは、検証できる具体さで書くこと、1ファイル200行未満を目安にすることを勧めています。曖昧な「きれいに書く」ではなく、コマンドと型の選び方を書くのがコツです。

Axumの場合、ドキュメントの記述から次の規約を起こせます。

## Rust / Axum 規約
- 変更後は必ず `cargo check` を実行し、エラーがなくなるまで直す
- 仕上げに `cargo clippy` と `cargo test` を実行する
- 共有状態は `State` extractor と `Arc` を使う(`Extension` は使わない)
- ハンドラーの引数は、Path・Query・State を先に、Json 等の本文は最後に置く
- 型エラーの原因が分からないハンドラーには `#[debug_handler]` を付けて再実行し、直ったら外す
- 開発時の待ち受けは 127.0.0.1 にする

各行には、ドキュメント上の裏付けがあります。

  • 共有状態はStateが型安全で、Axumの解説でも優先が勧められています。Extensionは、レイヤーの付け忘れや型違いがあると、コンパイルでなく実行時に500エラーになります
  • 引数の順序は、Handlerの条件(最後の引数だけがFromRequest、それ以外はFromRequestPartsを実装)から導けます
  • Router<S>は状態Sが未提供のルーターで、.with_state(s)で状態を渡すとserve()に渡せるRouter<()>になります

パス指定は、サンプルコードが/users/{id}の形式です。コードを自分のプロジェクトに写すときは、この記法に揃えるよう規約に1行足しておくと、書き方の混在を避けられます。

これ以外のRustプロジェクトでも、同じ作りの規約が使えます。BevyのRustゲーム開発ではCargo権限とhooksの組み合わせを、Tauriアプリの開発ではRust側とフロント側をまたぐ構成を扱っています。

cargoコマンドをpermissionsで許可する

型エラーを直す反復は、Claudeがコマンドを何十回も実行します。毎回の確認を省くため、.claude/settings.jsonに許可ルールを書きます。

{
  "permissions": {
    "allow": [
      "Bash(cargo check *)",
      "Bash(cargo clippy *)",
      "Bash(cargo test *)",
      "Bash(cargo fmt *)"
    ],
    "ask": [
      "Bash(cargo add *)"
    ],
    "deny": [
      "Bash(cargo publish *)"
    ]
  }
}

ルール記法で押さえておく点は次の4つです。

  1. 末尾に空白を挟んだ*は、引数なしの素のコマンドにも一致します。Bash(cargo check *)はcargo checkにも一致します
  2. *はサブコマンドの後ろに置きます。Bash(cargo *)と書くとcargo publishやcargo installまで通ってしまうため、動詞ごとに分けるほうが境界が明確です
  3. cargo fmt && cargo checkのような連結コマンドは、サブコマンドごとに独立して照合されます。両方が許可されていれば通ります
  4. denyはallowより優先され、allowで例外を作れません。公開系のコマンドを止めたいときに向きます

このBashルールは、Claudeが書いたコマンド文字列に対するものです。引数を絞る類いのルールは回避されやすい、と権限の解説ページも注意しています。強い制限が必要なら、サンドボックスなど別の層と組み合わせます。また、プロジェクトの.claude/settings.jsonにあるallowルールは、フォルダーの信頼ダイアログを承認するまで有効になりません。

Spring BootのREST API開発でも、ビルドツールのコマンドを許可する同じ型の設定を使います。言語が変わっても、許可する動詞を絞る考え方は共通です。

型エラーをcargo checkの出力で直させる反復

AxumでよくあるのがHandlerトレイトの未実装エラーです。handlerモジュールの解説には、bool型を引数にした関数をルートに渡したときの例が載っています。

error[E0277]: the trait bound `fn(bool) -> impl Future {handler}: Handler<_, _>` is not satisfied

このエラーは、関数がHandlerを実装していないことしか教えてくれません。同ページは「なぜ実装していないのかをこのエラーは示さない」と書き、debug_handlerマクロ(axum-macrosクレート由来)で改善することを案内しています。

反復の回し方

Claudeには、次のような指示を渡します。コードの形は、ドキュメントの例に沿った書き方です。

cargo check を実行して、出たエラーをすべて直して。
Handler が実装されていないというエラーなら、対象の関数に
#[debug_handler] を付けて再実行し、出たメッセージに従って直す。
直ったら #[debug_handler] は外して、もう一度 cargo check を通す。

流れは4段階です。

手順

型エラーの修正ループ

  1. 1

    cargo checkを実行する

    エラー全文をClaudeに読ませます。長いメッセージでも要約させず、そのまま渡します。

  2. 2

    debug_handlerで原因を絞る

    Handler未実装のときは#[debug_handler]を付けて再実行します。debug_handlerのページの例では、非同期でない関数に付けると「handlers must be async functions」と原因が出ます。

  3. 3

    原因に沿って直す

    引数の順序・戻り値・Sendの欠落など、出た原因だけを直します。関係のない書き換えはさせません。

  4. 4

    マクロを外して再確認する

    直ったら属性を外し、cargo checkで通ることを確かめます。

Handlerの条件と、典型的な直し方

Handlerの条件は5つあります。エラーの読み方の索引として使えます。

Handlerの条件破ったときに起きること直し方の方向
asyncな関数である破ったときに起きることdebug_handlerが「async関数であること」を要求する直し方の方向async fnにする
引数は16個以下で、すべてSend破ったときに起きることHandler未実装のエラーになる直し方の方向引数を構造体にまとめる、Sendでない型を除く
最後以外の引数はFromRequestPartsを実装破ったときに起きること本文を取る型を途中に置くと満たせない直し方の方向Path・Query・Stateを先、Jsonなどを最後にする
最後の引数がFromRequestを実装破ったときに起きること通常のextractorでない型を渡している直し方の方向extractorの型に包む
戻り値がIntoResponse破ったときに起きること戻り値がレスポンスに変換できない直し方の方向Json・StatusCode・Resultなどで返す

もう1つ、futureがSendであることも条件です。!Sendな型をawaitをまたいで保持すると外れます。debug_handlerにも使えない場面があります。

  • 状態の型は、State引数がなければ()を前提に検査されます。状態を持つハンドラーでは#[debug_handler(state = AppState)]のように指定します
  • selfを持たないimplブロック内の関数には使えません
  • リリースビルド(cargo build --release)では何も起きません

この3点を知らないと、マクロを付けたのにエラーが別の形で出て混乱します。規約に「debug_handlerは開発ビルド専用」と書いておくと、Claudeが本番ビルドで効くと思い込むのを防げます。

clippyとテストを回す

cargo checkが通ったら、仕上げにcargo clippyを実行します。引数なしで動かすと、clippyの使い方ページによるとclippy::allグループの既定のlintが走ります。CI向けに警告をエラー扱いにするなら、cargo clippy -- -Dwarningsの形を使えます。ただし同ページは、この形はビルドキャッシュを無効にするため新しいCargoでは別の設定を勧めている、とも書いています。使うCargoのバージョンに合わせて選びます。

cargo check
cargo clippy
cargo test

clippyの指摘は、restrictionグループを丸ごと有効にしないよう注意します。同ページは、このグループは互いに矛盾するlintを含むので、必要なものを選んで有効にするよう案内しています。規約に「pedanticやrestrictionは勝手に有効化しない」と書いておくと、Claudeがlintを増やして指摘の洪水を作る事態を避けられます。

テストでは、Axumの解説はtowerを依存に加える理由として「テストに役立つ」と述べ、リポジトリ内のtestingサンプルを案内しています。同じ「テストを先に定義して回す」考え方は、pytestの規約をCLAUDE.mdに書く方法でも扱っています。

編集のたびに整形したいとき

ファイルを編集するたびに整形を走らせたい場合は、hooksのPostToolUseが使えます。hooksのガイドは、Edit|WriteをマッチャーにしてPrettierを走らせる例を示しています。Rustでは、同じ形でcargo fmtを実行する構成が考えられます。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "cargo fmt" }
        ]
      }
    ]
  }
}

cargo checkまで毎回の編集で走らせるとコンパイル時間が積み重なるため、hooksには整形のような軽い処理だけを置き、check・clippy・testはClaudeに明示的に実行させるほうが扱いやすくなります。

つまずきやすい点

Axum特有の落とし穴を3つ挙げます。いずれもAxumのドキュメントに記述があります。

  • 状態をExtensionで渡すと実行時に落ちる: 型が合っていても、.layer(Extension(...))を付け忘れるとコンパイルは通り、リクエスト時に500エラーになります。cargo checkでは見つからないため、テストで経路を叩く必要があります
  • Router<S>のままserve()に渡せない: Router<S>は状態Sが未提供の状態を表します。with_stateを呼んでRouter<()>にしてから渡します
  • FromRefのderiveにはmacros機能が必要: 部分状態を取り出す#[derive(FromRef)]を使うなら、axumの機能フラグでmacrosを有効にします。macrosは既定では無効です

Web UI側をあわせて作るなら、SvelteKitの開発のように、型検査コマンドを規約に入れる手順が参考になります。

規約と許可の2点セットで反復を安定させる

AxumのRust APIをClaude Codeで進めるときは、「CLAUDE.mdに検証コマンドと型の選び方を書く」「cargo check・clippy・testをサブコマンド単位で許可する」の2点を先に整えます。型エラーの直しは、cargo checkの全文を渡し、Handler未実装ならdebug_handlerで原因を出させ、直ったら外す、という短いループです。

何を許可し、何を毎回確認させるかは、プロジェクトの事情で変わります。公開系のcargo publishはdenyに、依存を増やすcargo addはaskに置く構成から始めて、実際の作業で止まる箇所に合わせて調整するのが現実的です。

この記事を共有:XはてブLinkedIn