Use Cases

Desarrollo en Rust con Claude Code

Aprenda sobre desarrollo en Rust usando Claude Code. Incluye consejos practicos y ejemplos de codigo.

Rust開発でClaude Codeが活躍する場面

Rustは所有権、ライフタイム、トレイトなど独自の概念が多く、学習コストの高い言語です。Claude Codeはこれらの概念を正確に理解し、コンパイルが通るコードを高い精度で生成できます。特にエラーハンドリングや非同期処理の定型パターンで威力を発揮します。

CLIツールの開発

> clapを使ったCLIツールを作成して。
> JSONファイルを読み込んでCSVに変換する機能で。
> プログレスバーとエラーハンドリングも実装して。
// src/main.rs
use clap::Parser;
use anyhow::{Context, Result};
use indicatif::{ProgressBar, ProgressStyle};
use serde_json::Value;
use std::fs;
use std::io::Write;

#[derive(Parser, Debug)]
#[command(name = "json2csv", about = "Convert JSON to CSV")]
struct Args {
    /// Input JSON file path
    #[arg(short, long)]
    input: String,

    /// Output CSV file path
    #[arg(short, long)]
    output: String,

    /// Fields to extract (comma-separated)
    #[arg(short, long)]
    fields: Option<String>,
}

fn main() -> Result<()> {
    let args = Args::parse();

    let content = fs::read_to_string(&args.input)
        .context(format!("Failed to read file: {}", args.input))?;

    let data: Vec<Value> = serde_json::from_str(&content)
        .context("Failed to parse JSON")?;

    if data.is_empty() {
        println!("No data found in the JSON file.");
        return Ok(());
    }

    // フィールドの決定
    let fields: Vec<String> = match &args.fields {
        Some(f) => f.split(',').map(|s| s.trim().to_string()).collect(),
        None => {
            if let Some(obj) = data[0].as_object() {
                obj.keys().cloned().collect()
            } else {
                anyhow::bail!("JSON array elements must be objects");
            }
        }
    };

    let pb = ProgressBar::new(data.len() as u64);
    pb.set_style(
        ProgressStyle::with_template(
            "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta})"
        )?
        .progress_chars("=>-"),
    );

    let mut output = fs::File::create(&args.output)
        .context(format!("Failed to create output file: {}", args.output))?;

    // Header
    writeln!(output, "{}", fields.join(","))?;

    // データ行
    for item in &data {
        let row: Vec<String> = fields.iter().map(|field| {
            match item.get(field) {
                Some(Value::String(s)) => format!("\"{}\"", s.replace('"', "\"\"")),
                Some(v) => v.to_string(),
                None => String::new(),
            }
        }).collect();

        writeln!(output, "{}", row.join(","))?;
        pb.inc(1);
    }

    pb.finish_with_message("Done");
    println!("Converted {} records to {}", data.len(), args.output);

    Ok(())
}
# Cargo.toml
[package]
name = "json2csv"
version = "0.1.0"
edition = "2021"

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

Web APIの開発(Axum)

> Axumを使ったREST APIを作成して。
> CRUD操作、バリデーション、エラーハンドリングを実装して。
// src/main.rs
use axum::{
    extract::{Path, State},
    http::StatusCode,
    response::IntoResponse,
    routing::{get, post, put, delete},
    Json, Router,
};
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use uuid::Uuid;

#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)]
struct Task {
    id: Uuid,
    title: String,
    description: Option<String>,
    completed: bool,
    created_at: chrono::NaiveDateTime,
}

#[derive(Debug, Deserialize)]
struct CreateTask {
    title: String,
    description: Option<String>,
}

#[derive(Debug, Deserialize)]
struct UpdateTask {
    title: Option<String>,
    description: Option<String>,
    completed: Option<bool>,
}

#[derive(Clone)]
struct AppState {
    db: PgPool,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let db = PgPool::connect(&std::env::var("DATABASE_URL")?).await?;
    sqlx::migrate!().run(&db).await?;

    let state = AppState { db };

    let app = Router::new()
        .route("/api/tasks", get(list_tasks).post(create_task))
        .route("/api/tasks/:id", get(get_task).put(update_task).delete(delete_task))
        .with_state(state);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
    println!("Server running on http://localhost:3000");
    axum::serve(listener, app).await?;

    Ok(())
}

async fn list_tasks(State(state): State<AppState>) -> impl IntoResponse {
    let tasks = sqlx::query_as::<_, Task>("SELECT * FROM tasks ORDER BY created_at DESC")
        .fetch_all(&state.db)
        .await;

    match tasks {
        Ok(tasks) => Json(tasks).into_response(),
        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
    }
}

async fn create_task(
    State(state): State<AppState>,
    Json(input): Json<CreateTask>,
) -> impl IntoResponse {
    if input.title.trim().is_empty() {
        return (StatusCode::BAD_REQUEST, "Title is required").into_response();
    }

    let task = sqlx::query_as::<_, Task>(
        "INSERT INTO tasks (id, title, description) VALUES ($1, $2, $3) RETURNING *"
    )
    .bind(Uuid::new_v4())
    .bind(&input.title)
    .bind(&input.description)
    .fetch_one(&state.db)
    .await;

    match task {
        Ok(task) => (StatusCode::CREATED, Json(task)).into_response(),
        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
    }
}

async fn get_task(
    State(state): State<AppState>,
    Path(id): Path<Uuid>,
) -> impl IntoResponse {
    let task = sqlx::query_as::<_, Task>("SELECT * FROM tasks WHERE id = $1")
        .bind(id)
        .fetch_optional(&state.db)
        .await;

    match task {
        Ok(Some(task)) => Json(task).into_response(),
        Ok(None) => StatusCode::NOT_FOUND.into_response(),
        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
    }
}

async fn update_task(
    State(state): State<AppState>,
    Path(id): Path<Uuid>,
    Json(input): Json<UpdateTask>,
) -> impl IntoResponse {
    let task = sqlx::query_as::<_, Task>(
        "UPDATE tasks SET title = COALESCE($2, title), description = COALESCE($3, description), completed = COALESCE($4, completed) WHERE id = $1 RETURNING *"
    )
    .bind(id)
    .bind(&input.title)
    .bind(&input.description)
    .bind(input.completed)
    .fetch_optional(&state.db)
    .await;

    match task {
        Ok(Some(task)) => Json(task).into_response(),
        Ok(None) => StatusCode::NOT_FOUND.into_response(),
        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
    }
}

async fn delete_task(
    State(state): State<AppState>,
    Path(id): Path<Uuid>,
) -> impl IntoResponse {
    let result = sqlx::query("DELETE FROM tasks WHERE id = $1")
        .bind(id)
        .execute(&state.db)
        .await;

    match result {
        Ok(r) if r.rows_affected() > 0 => StatusCode::NO_CONTENT.into_response(),
        Ok(_) => StatusCode::NOT_FOUND.into_response(),
        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
    }
}

エラーハンドリングのパターン

Claude Codeに依頼する際、Rustらしいエラーハンドリングを指示できます。

> カスタムエラー型をthiserrorで定義して。
> APIレスポンスへの変換も実装して。
use axum::{http::StatusCode, response::IntoResponse, Json};
use thiserror::Error;

#[derive(Error, Debug)]
pub enum AppError {
    #[error("Not found: {0}")]
    NotFound(String),

    #[error("Validation error: {0}")]
    Validation(String),

    #[error("Database error: {0}")]
    Database(#[from] sqlx::Error),

    #[error("Internal error: {0}")]
    Internal(#[from] anyhow::Error),
}

impl IntoResponse for AppError {
    fn into_response(self) -> axum::response::Response {
        let (status, message) = match &self {
            AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()),
            AppError::Validation(msg) => (StatusCode::BAD_REQUEST, msg.clone()),
            AppError::Database(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Database error".into()),
            AppError::Internal(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Internal error".into()),
        };

        (status, Json(serde_json::json!({ "error": message }))).into_response()
    }
}

Summary

Claude Codeを使えば、Rustの所有権やライフタイムを正しく扱ったコードを効率的に生成できます。CLIツール、Web API、エラーハンドリングなど、Rust特有のパターンに精通しているため、コンパイルエラーとの戦いを大幅に減らせます。他の言語との比較はClaude Code vs GitHub Copilotを参照してください。効率的な使い方は生産性を3倍にするTipsで紹介しています。

Claude Codeの詳細はAnthropic公式ドキュメントをご覧ください。Rustの学習にはThe Rust Programming Languageが最適です。

#Claude Code #Rust #CLI #Web API #システムプログラミング