mcpField
R
rust-project

rust-project

github.com/majiayu000开发28884 次安装1 天前更新
代码仓库

关于 rust-project

为 Rust 项目提供架构与技术选型指南,适用于从单 crate 应用到多 crate 工作区的开发。内容涵盖异步运行时、Web 框架、数据库、错误处理及分层结构,并提供配置示例。

主要功能

  1. 01Rust 项目架构原则与技术选型
  2. 02单 crate 与工作区结构示例
  3. 03Tokio 异步及 tracing 可观测性建议
  4. 04错误处理与分层设计模式
  5. 05Web、数据库和 CLI 方案参考

适用场景

  1. 01规划 Rust 服务或 CLI 项目的目录结构
  2. 02为异步 Web 服务选择组件
  3. 03组织多 crate 工作区及应用层次

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/majiayu000/spellbook --skill rust-project

安装后在 Claude Code 中输入/rust-project确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

reference/
architecture.md
extended.md
patterns.md
tech-stack.md
templates/cli/
Cargo.toml
templates/cli/src/
error.rs
main.rs
templates/cli/src/commands/
mod.rs
process.rs
templates/web-service/
.env.example
Cargo.toml
Makefile
templates/web-service/config/
default.toml
templates/web-service/migrations/
001_init.sql
templates/web-service/src/
config.rs
db.rs
error.rs
lib.rs
main.rs
router.rs
templates/web-service/src/handlers/
health.rs
mod.rs
user.rs
templates/web-service/src/models/
mod.rs
user.rs
templates/web-service/src/services/
mod.rs
user.rs

详细介绍

Rust 项目架构

核心原则

  • 所有权优先 — 拥抱借用检查器,避免不必要的克隆
  • 零成本抽象 — Newtype、迭代器、async/await
  • 通过工作区扩展规模 — 多 crate 项目使用 Cargo 工作区
  • 精准处理错误 — 库使用 thiserror,应用使用 anyhow
  • 使用 Tokio 实现异步 — Tokio 运行时 + tracing 实现可观测性
  • 不考虑向后兼容 — 删除,不要弃用。直接修改
  • 使用 LiteLLM 调用大模型 API — 所有大模型集成均使用 LiteLLM 代理

不考虑向后兼容

删除未使用的代码。直接修改。不添加兼容层。

// ❌ BAD: Deprecated attribute kept around
#[deprecated(since = "0.2.0", note = "Use new_function instead")]
pub fn old_function() { ... }

// ❌ BAD: Type alias for renamed types
pub type OldName = NewName; // "for backwards compatibility"

// ❌ BAD: Unused parameters
fn process(_legacy: &str, data: &Data) { ... }

// ❌ BAD: Feature flags for old behavior
#[cfg(feature = "legacy")]
fn old_impl() { ... }

// ✅ GOOD: Just delete and update all usages
pub fn new_function() { ... }
// Then: Find & replace all old_function → new_function

// ✅ GOOD: Remove unused parameters entirely
fn process(data: &Data) { ... }

使用 LiteLLM 调用大模型 API

使用 LiteLLM 代理。不要直接调用提供商 API。

// src/llm.rs
use async_openai::{Client, config::OpenAIConfig};

pub fn create_client(base_url: &str, api_key: &str) -> Client<OpenAIConfig> {
    let config = OpenAIConfig::new()
        .with_api_base(base_url)  // LiteLLM proxy URL
        .with_api_key(api_key);
    Client::with_config(config)
}

// Usage: connect to LiteLLM, use any model
let client = create_client("http://localhost:4000", &api_key);
let request = CreateChatCompletionRequestArgs::default()
    .model("gpt-4o")  // or "claude-3-opus", "gemini-pro", etc.
    .messages(vec![...])
    .build()?;

快速开始

1. 初始化项目
# Simple project
cargo new myapp
cd myapp

# Workspace project
mkdir myapp && cd myapp
cargo init --name app
2. 应用技术栈
层推荐方案
异步运行时Tokio
Web 框架Axum
序列化Serde
ORM / 数据库SeaORM(异步、Active Record)
CLIClap(derive)
错误处理(库)thiserror
错误处理(应用)anyhow
日志记录tracing + tracing-subscriber
HTTP 客户端reqwest
配置config-rs
Web 框架选择
框架适用场景
Axum(默认)现代微服务、Tokio 生态系统、容器部署、Tower 中间件
Actix Web追求最高吞吐量、以 WebSocket 为主、需要成熟生态系统
Rocket快速原型开发、小型团队、尽量减少样板代码

对于大多数项目,Axum 在性能、易用性和 Tokio 集成方面实现了最佳平衡。

数据库 / ORM 选择
库适用场景
SeaORM(默认)CRUD 密集型服务、快速开发、异步优先、跨数据库测试
SQLx需要控制原始 SQL、追求最高性能、需要编译时 SQL 校验
Diesel需要编译时类型安全、模式稳定、同步工作负载

推荐使用 SeaORM,因为它具备 Active Record 的易用性、原生异步支持,并能与 Axum 无缝集成。

版本策略

始终使用最新版本。模板中不要固定版本。

[dependencies]
tokio = { version = "*", features = ["full"] }
axum = "*"
serde = { version = "*", features = ["derive"] }

# cargo update fetches latest compatible versions
# Cargo.lock ensures reproducible builds
3. 选择项目结构
简单项目(单 crate)
myapp/
├── Cargo.toml
├── src/
│   ├── main.rs           # Entry point
│   ├── lib.rs            # Library root (optional)
│   ├── config.rs         # Configuration
│   ├── error.rs          # Error types
│   ├── handlers/         # HTTP handlers (web)
│   │   └── mod.rs
│   ├── services/         # Business logic
│   │   └── mod.rs
│   └── models/           # Domain types
│       └── mod.rs
├── tests/                # Integration tests
│   └── api_test.rs
└── benches/              # Benchmarks
    └── bench.rs
工作区项目(多 crate)
myapp/
├── Cargo.toml            # Workspace manifest
├── crates/
│   ├── app/              # Binary crate
│   │   ├── Cargo.toml
│   │   └── src/main.rs
│   ├── core/             # Business logic lib
│   │   ├── Cargo.toml
│   │   └── src/lib.rs
│   └── infra/            # Infrastructure lib
│       ├── Cargo.toml
│       └── src/lib.rs
├── config/
│   └── default.toml
└── Makefile

架构层

main.rs — 入口

连接依赖并启动运行时。不包含业务逻辑。

// src/main.rs
use anyhow::Result;
use sea_orm::Database;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

#[tokio::main]
async fn main() -> Result<()> {
    // Initialize tracing
    tracing_subscriber::registry()
        .with(tracing_subscriber::fmt::layer())
        .init();

    // Load config
    let config = myapp::config::load()?;

    // Connect to database (SeaORM)
    let db = Database::connect(&config.database_url).await?;

    // Build application state
    let state = myapp::AppState::new(db);

    // Build router
    let app = myapp::router::build(state);

    // Run server
    let listener = tokio::net::TcpListener::bind(&config.listen_addr).await?;
    tracing::info!("listening on {}", config.listen_addr);
    axum::serve(listener, app).await?;

    Ok(())
}
lib.rs — 库根

重新导出公共 API,定义 AppState。

// src/lib.rs
pub mod config;
pub mod db;
pub mod error;
pub mod handlers;
pub mod models;  // SeaORM entities
pub mod router;
pub mod services;

use sea_orm::DatabaseConnection;
use std::sync::Arc;

pub struct AppState {
    pub db: DatabaseConnection,
}

impl AppState {
    pub fn new(db: DatabaseConnection) -> Arc<Self> {
        Arc::new(Self { db })
    }
}
error.rs — 错误处理
// src/error.rs
use axum::{http::StatusCode, response::{IntoResponse, Response}, Json};
use sea_orm::DbErr;
use serde_json::json;

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

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

    #[error("unauthorized")]
    Unauthorized,

    #[error("internal error")]
    Internal(#[from] anyhow::Error),

    #[error("database error: {0}")]
    Database(#[from] DbErr),
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let (status, message) = match &self {
            AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()),
            AppError::Validation(msg) => (StatusCode::BAD_REQUEST, msg.clone()),
            AppError::Unauthorized => (StatusCode::UNAUTHORIZED, "unauthorized".into()),
            AppError::Internal(_) | AppError::Database(_) => {
                tracing::error!("Internal error: {:?}", self);
                (StatusCode::INTERNAL_SERVER_ERROR, "internal error".into())
            }
        };

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

pub type Result<T> = std::result::Result<T, AppError>;
handlers/ — HTTP 层
// src/handlers/user.rs
use axum::{extract::{Path, State}, Json};
use std::sync::Arc;
use crate::{error::Result, models::user, services, AppState};

pub async fn get_user(
    State(state): State<Arc<AppState>>,
    Path(id): Path<i64>,
) -> Result<Json<user::Model>> {
    let user = services::user::find_by_id(&state.db, id).await?;
    Ok(Json(user))
}

pub async fn create_user(
    State(state): State<Arc<AppState>>,
    Json(input): Json<CreateUserInput>,
) -> Result<Json<user::Model>> {
    let user = services::user::create(&state.db, input).await?;
    Ok(Json(user))
}
services/ — 业务逻辑
// src/services/user.rs
use sea_orm::{ActiveModelTrait, DatabaseConnection, EntityTrait, Set};
use crate::{error::{AppError, Result}, models::user};

pub async fn find_by_id(db: &DatabaseConnection, id: i64) -> Result<user::Model> {
    user::Entity::find_by_id(id)
        .one(db)
        .await?
        .ok_or_else(|| AppError::NotFound(format!("user {}", id)))
}

pub async fn create(db: &DatabaseConnection, input: CreateUserInput) -> Result<user::Model> {
    let new_user = user::ActiveModel {
        email: Set(input.email),
        name: Set(input.name),
        ..Default::default()
    };

    let user = new_user.insert(db).await?;
    Ok(user)
}

// Find with relations
pub async fn find_with_posts(db: &DatabaseConnection, id: i64) -> Result<(user::Model, Vec<post::Model>)> {
    user::Entity::find_by_id(id)
        .find_with_related(post::Entity)
        .all(db)
        .await?
        .into_iter()
        .next()
        .ok_or_else(|| AppError::NotFound(format!("user {}", id)))
}

工作区配置

# Cargo.toml (workspace root)
[workspace]
resolver = "3"
members = ["crates/*"]

[workspace.package]
version = "0.1.0"
edition = "2024"
license = "MIT"

[workspace.dependencies]
tokio = { version = "*", features = ["full"] }
axum = "*"
serde = { version = "*", features = ["derive"] }
serde_json = "*"
sea-orm = { version = "*", features = ["sqlx-postgres", "runtime-tokio-native-tls"] }
thiserror = "*"
anyhow = "*"
tracing = "*"
tracing-subscriber = "*"
# crates/app/Cargo.toml
[package]
name = "app"
version.workspace = true
edition.workspace = true

[dependencies]
core.path = "../core"
infra.path = "../infra"
tokio.workspace = true
axum.workspace = true
anyhow.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true

CLI 应用

// src/main.rs
use clap::Parser;
use anyhow::Result;

#[derive(Parser)]
#[command(name = "myapp", version, about)]
struct Cli {
    /// Input file path
    #[arg(short, long)]
    input: PathBuf,

    /// Output format
    #[arg(short, long, default_value = "json")]
    format: OutputFormat,

    /// Verbose output
    #[arg(short, long)]
    verbose: bool,
}

#[derive(Clone, clap::ValueEnum)]
enum OutputFormat {
    Json,
    Yaml,
    Text,
}

fn main() -> Result<()> {
    let cli = Cli::parse();

    if cli.verbose {
        tracing_subscriber::fmt::init();
    }

    // Process input...
    Ok(())
}

测试

// tests/api_test.rs
use axum::{body::Body, http::{Request, StatusCode}};
use tower::ServiceExt;

#[tokio::test]
async fn test_get_user() {
    let app = create_test_app().await;

    let response = app
        .oneshot(
            Request::builder()
                .uri("/users/1")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();

    assert_eq!(response.status(), StatusCode::OK);
}

// Unit test with mock
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_validate_email() {
        assert!(validate_email("[email protected]").is_ok());
        assert!(validate_email("invalid").is_err());
    }
}

扩展参考

为保持此技能精简,从 ## Makefile 开始的详细内容已移至 reference/extended.md。如果任务需要其中移出的示例、命令目录、检查清单、平台详情或实现模板,请加载该参考文件。

评论

支持 Markdown