Développement Rust avec Claude Code
Découvrez développement Rust avec Claude Code. Conseils pratiques et exemples de code inclus.
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が最適です。
Related Posts
Comment booster vos projets personnels avec Claude Code [Avec exemples]
Apprenez à accélérer considérablement vos projets de développement personnels avec Claude Code. Inclut des exemples concrets et un workflow pratique de l'idée au déploiement.
Comment automatiser le refactoring avec Claude Code
Apprenez à automatiser efficacement le refactoring de code avec Claude Code. Inclut des prompts pratiques et des patterns de refactoring concrets pour des projets réels.
Guide complet de configuration CORS avec Claude Code
Découvrez le guide complet de configuration CORS avec Claude Code. Conseils pratiques et exemples de code inclus.