Use Cases (更新: 2026/6/2)

Claude CodeでRust開発入門:Cargo、所有権、テスト、CLI実装まで

Claude CodeでRust開発を始める実践ガイド。Cargo設定、所有権、テスト、clippy/fmt、CLI実装を初心者向けに解説。

Claude CodeでRust開発入門:Cargo、所有権、テスト、CLI実装まで

Claude CodeでRustを始める前に決めること

Rustは、所有権、借用、ライフタイム、Resultによるエラー処理など、最初に覚える概念が多い言語です。けれど、難しいからClaude Codeに丸投げする、という使い方は危険です。Rustの強みは、コンパイラが危ない状態を早めに止めてくれることにあります。Claude Codeには、そのコンパイラの声を一緒に読み、なぜ直すのかを言語化させると学習と実装が同時に進みます。

この記事では、初心者が小さなCLIを作りながら、Cargoプロジェクトの作成、所有権と借用の質問、単体テスト、cargo fmtcargo clippyResultを使ったエラー処理、そして安全なリファクタリング依頼までを一通り扱います。公式情報は Rust Bookの所有権Cargo Bookの新規プロジェクト作成cargo testrustfmtClippyClaude Code overviewを基準にします。

Claude Code自体の導入がまだなら、先にClaude Code入門ガイドを読み、プロジェクトルールはCLAUDE.mdベストプラクティスにまとめておくと、このRust記事の手順を再利用しやすくなります。

flowchart LR
  Prompt["目的と制約を伝える"]
  Cargo["Cargoで小さく作る"]
  Compiler["所有権エラーを読む"]
  Tests["cargo testで固定する"]
  Quality["fmtとclippyで整える"]
  Refactor["差分を絞ってリファクタ"]

  Prompt --> Cargo --> Compiler --> Tests --> Quality --> Refactor

Cargoプロジェクトを最小構成で作る

Rust開発では、まずCargoを使います。CargoはRustのビルド、依存関係、テスト、実行をまとめて扱う標準ツールです。Claude Codeに「RustでCLIを作って」とだけ頼む前に、どのコマンドで検証するかを先に決めます。

cargo new tasknote --bin
cd tasknote
cargo run

cargo newは、Cargo.tomlsrc/main.rsを作ります。いまのCargo Bookでは、バイナリプログラムなら--binを使う例が案内されています。最近のRustではedition = "2024"が生成される環境もあるため、Claude Codeに古い記事のedition = "2018"をそのまま持ってこさせないように、現物のCargo.tomlを読ませます。

依存関係は最初から増やしすぎません。今回使うのは、引数処理のclapと、エラーに文脈を足すanyhowだけです。

[package]
name = "tasknote"
version = "0.1.0"
edition = "2024"

[dependencies]
anyhow = "1"
clap = { version = "4", features = ["derive"] }

Claude Codeへの最初の依頼は、次のように範囲を狭めます。

このCargoプロジェクトで、小さなCLI `tasknote` を作ります。
目的は、`tasks.txt` にある `[ ] task` と `[x] task` を読み、完了数と未完了数を表示することです。
まず `Cargo.toml`、`src/lib.rs`、`src/main.rs` の設計案だけ出してください。
編集はまだしないでください。所有権とエラー処理の方針も説明してください。

「編集はまだしない」と一度止めるのが大事です。Rustは設計の段階で、文字列を所有するのか、参照で渡すのか、エラーをpanic!にするのかResultで返すのかを決める場面が多いからです。

所有権と借用をClaude Codeに質問する

所有権は、値の持ち主を一つに決めるRustのルールです。借用は、値を移動させずに一時的に参照することです。ライフタイムは、参照が有効でいられる範囲をコンパイラに説明する仕組みです。最初は専門用語で覚えるより、「誰がデータを持つか」「誰が一時的に読むだけか」と言い換えると理解しやすくなります。

今回のCLIでは、ファイルから読んだ文字列はread_tasksが受け取り、解析した結果はVec<Task>として所有します。summarizeはタスク一覧を読むだけなので、&[Task]を借用します。これをClaude Codeに説明させると、無駄なclone()を増やさずに済みます。

`parse_tasks(input: &str) -> Vec<Task>` と `summarize(tasks: &[Task]) -> String` の所有権を初心者向けに説明してください。
`String`、`&str`、`Vec<Task>`、`&[Task]` がそれぞれ何を持つのかを、今回のCLIの例に限定して説明してください。
不要な `clone()` を入れたくありません。

よくある失敗は、コンパイルエラーを早く消したくて、Claude Codeに「全部cloneして直して」と頼むことです。clone()は悪ではありませんが、理由なく増えるとメモリ使用量も設計意図も見えにくくなります。借用で済む関数、所有してよい構造体、外部へ返すデータを分けて質問しましょう。

コピペで動かせる小さなCLI例

まず、ロジックをsrc/lib.rsへ置きます。main.rsに全部書くより、テストしやすく、あとでClaude Codeにリファクタリングを頼む時も差分が小さくなります。

// src/lib.rs
use anyhow::{Context, Result};
use std::{fs, path::Path};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Task {
    pub title: String,
    pub done: bool,
}

pub fn parse_tasks(input: &str) -> Vec<Task> {
    input.lines().filter_map(parse_task_line).collect()
}

fn parse_task_line(line: &str) -> Option<Task> {
    let trimmed = line.trim();

    if let Some(title) = trimmed
        .strip_prefix("[x] ")
        .or_else(|| trimmed.strip_prefix("[X] "))
    {
        return Some(Task {
            title: title.trim().to_string(),
            done: true,
        });
    }

    if let Some(title) = trimmed.strip_prefix("[ ] ") {
        return Some(Task {
            title: title.trim().to_string(),
            done: false,
        });
    }

    None
}

pub fn summarize(tasks: &[Task]) -> String {
    let total = tasks.len();
    let done = tasks.iter().filter(|task| task.done).count();
    let open = total.saturating_sub(done);

    format!("{total} tasks: {done} done, {open} open")
}

pub fn read_tasks(path: impl AsRef<Path>) -> Result<Vec<Task>> {
    let path = path.as_ref();
    let content = fs::read_to_string(path)
        .with_context(|| format!("failed to read {}", path.display()))?;

    Ok(parse_tasks(&content))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parses_markdown_style_tasks() {
        let tasks = parse_tasks("[ ] write parser\n[x] add tests\n[X] run clippy\n");

        assert_eq!(
            tasks,
            vec![
                Task {
                    title: "write parser".to_string(),
                    done: false,
                },
                Task {
                    title: "add tests".to_string(),
                    done: true,
                },
                Task {
                    title: "run clippy".to_string(),
                    done: true,
                },
            ]
        );
    }

    #[test]
    fn ignores_unrecognized_lines() {
        let tasks = parse_tasks("# Sprint notes\n- plain bullet\n[ ] keep this\n");

        assert_eq!(tasks.len(), 1);
        assert_eq!(tasks[0].title, "keep this");
    }

    #[test]
    fn summarizes_counts() {
        let tasks = parse_tasks("[ ] one\n[x] two\n[ ] three\n");

        assert_eq!(summarize(&tasks), "3 tasks: 1 done, 2 open");
    }
}

次に、CLIの入口をsrc/main.rsへ置きます。mainは引数を受け取り、ライブラリ関数を呼び出すだけにします。

// src/main.rs
use anyhow::Result;
use clap::Parser;
use std::path::PathBuf;
use tasknote::{read_tasks, summarize};

#[derive(Parser, Debug)]
#[command(name = "tasknote", about = "Summarize simple task files")]
struct Cli {
    #[arg(short, long, default_value = "tasks.txt")]
    file: PathBuf,

    #[arg(long)]
    only_open: bool,
}

fn main() -> Result<()> {
    let args = Cli::parse();
    let tasks = read_tasks(&args.file)?;

    if args.only_open {
        for task in tasks.iter().filter(|task| !task.done) {
            println!("- {}", task.title);
        }
    } else {
        println!("{}", summarize(&tasks));
    }

    Ok(())
}

試すファイルは次の形です。

[ ] write parser
[x] add unit tests
[ ] run clippy

実行コマンドはこの順番で十分です。

cargo fmt
cargo test
cargo clippy --all-targets -- -D warnings
cargo run -- --file tasks.txt
cargo run -- --file tasks.txt --only-open

ここでmainResult<()>を返しているので、ファイルが見つからない時はanyhowContextで「どのファイルを読めなかったか」が表示されます。Rust Bookのエラー処理章でも、回復可能なエラーにはResult<T, E>を使う考え方が説明されています。CLIではunwrap()で落とすより、ユーザーに読める文脈を返すほうが運用しやすいです。

テスト、fmt、clippyをClaude Codeの作業単位に入れる

Claude CodeにRustコードを書かせる時は、最後の指示に検証コマンドを必ず入れます。cargo testは単体テスト、統合テスト、ドキュメントテストを実行できます。cargo fmtはスタイルをそろえ、cargo clippyはよくあるミスや改善点を検出します。

`src/lib.rs` と `src/main.rs` を実装してください。
完了後に `cargo fmt`、`cargo test`、`cargo clippy --all-targets -- -D warnings` を実行してください。
失敗した場合は、エラー全文を要約してから最小差分で修正してください。
`unwrap()` はテスト以外では使わないでください。

この指示のポイントは、コード生成と検証を同じ作業単位にすることです。Claude Codeは公式ドキュメントで、コードベースの読み取り、編集、コマンド実行を行うagentic coding toolとして説明されています。ただし、AIの報告だけで安心せず、最後は自分でもgit diffとコマンド結果を確認します。権限を絞りたいチームはClaude Code権限設定ガイドも合わせて確認してください。

3つ以上の実務ユースケース

1つ目は、既存のRust CLIに小さな機能を足すケースです。たとえば--json出力を追加したい時、Claude Codeに「summarizeの戻り値を壊さず、JSON出力は別関数に分ける」と頼むと、既存テストを守りながら拡張できます。

2つ目は、所有権エラーの学習です。cannot move out ofborrowed value does not live long enoughのようなエラーが出たら、エラー全文、該当関数、期待するデータの寿命を渡します。「修正だけ」ではなく「なぜこの借用ではだめなのか」を説明させると、次に同じエラーを見た時に自分で判断できます。

3つ目は、テストファーストのバグ修正です。先に「空行、[X]、不正な行をどう扱うか」のテストを追加し、その後で実装を直します。Rustは型で多くの問題を防ぎますが、仕様のズレはテストで固定する必要があります。

4つ目は、安全なリファクタリングです。main.rsが太ってきたら、Claude Codeに「入出力、パース、表示を分離し、公開関数のシグネチャを変えない」と依頼します。大きな書き換えではなく、テストが通る単位で関数を移すのが現実的です。

初心者がつまずきやすい落とし穴

最初の落とし穴は、clone()で所有権エラーを全部消すことです。短期的には動きますが、データの持ち主が曖昧になります。Claude Codeには「このclone()は必要か。借用で済むか」と聞く癖をつけます。

2つ目は、unwrap()を本番CLIに残すことです。サンプルでは簡単に見えますが、ファイル読み込み、JSON解析、ネットワーク、設定ファイルでは失敗が普通に起きます。ResultContextで、ユーザーが次に何を直せばよいか分かるメッセージにします。

3つ目は、テストなしでリファクタリングすることです。Claude Codeは広い差分を作れますが、広い差分ほどレビューが難しくなります。先にcargo testで守るふるまいを固定し、cargo clippyで警告をエラー扱いにしてから進めます。

4つ目は、ワークスペース全体にcargo fmtをかけて不要な差分を増やすことです。既存プロジェクトでは、対象crate、変更ファイル、許可するコマンドを明確にします。複数人で触っているリポジトリなら、Claude Codeに「このタスクで触ってよいファイル」を明示します。

安全にリファクタリングを頼むプロンプト

リファクタリングでは、AIに「きれいにして」ではなく、守る条件、触る範囲、確認コマンドを渡します。

`tasknote` のパーサーを安全にリファクタリングしてください。

守る条件:
- 入力形式 `[ ] task` と `[x] task` の意味を変えない
- 公開関数 `parse_tasks`, `summarize`, `read_tasks` のシグネチャを変えない
- 触ってよいファイルは `src/lib.rs` とそのテストだけ
- 不要な `clone()` を増やさない
- まず変更計画を3行で出し、承認後に編集する

編集後:
- `cargo fmt`
- `cargo test`
- `cargo clippy --all-targets -- -D warnings`
- 差分の要約と、所有権まわりで変えた判断を報告

この形にすると、Claude Codeは「コードを動かすエージェントの足場」、つまり作業範囲と検証手順を持った相手として使えます。特にRustでは、コンパイラ、テスト、Clippyの3つをレビュー担当にする感覚が重要です。

収益導線と次の学習

個人で練習するなら、このCLIをserde_json対応、CSV出力、ディレクトリ走査へ広げるだけで十分な題材になります。チーム導入では、CLAUDE.mdにRust edition、禁止したいunwrap()、必須コマンド、レビュー観点、触ってよいcrateを明記してください。

ClaudeCodeLabでは、Claude Codeのプロンプト、CLAUDE.md、権限設定、レビュー手順を教材として整理しています。まず無料で確認したい方は無料チートシートへ、実務リポジトリに合わせてRust開発フローを整えたい場合はClaude Code研修・導入相談へ進んでください。関連記事として、テストの進め方はClaude CodeでTDD、安全な変更手順はレビュー用チェックリストも役立ちます。

この記事で紹介した内容を実際に試した結果、最初にsrc/lib.rsへロジックを分け、cargo testでふるまいを固定してからClaude Codeに修正を頼む流れが一番安定しました。所有権エラーも、エラー全文だけを渡すより「この関数は読むだけか、所有するか」を一緒に聞いたほうが、無駄なclone()の少ない差分になりました。

#Claude Code #Rust #CLI #テスト #リファクタリング
無料

無料PDF: Claude Code はじめてのチートシート

まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。

スパムは送りません。登録情報は厳重に管理します。

Claude Codeを仕事で使える形にしませんか?

無料PDFで基礎を固めたあと、すぐ使えるテンプレート集で試し、必要なら業務自動化や導入相談まで進められます。

Masa

この記事を書いた人

Masa

Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。

PR

関連書籍・参考図書

この記事のテーマに関連する書籍を楽天ブックスで探せます。

※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。