返回 Skill 列表
extension
分类: 开发与工程无需 API Key

rust-project-structure

Rust项目组织和结构。在创建新项目、组织模块、设置工作区、配置Cargo.toml或在不同版本之间迁移时使用。

person作者: jakexiaohubgithub

Rust Project Structure and Organization

Modern Rust project organization following 2024 Edition best practices.

Rust Editions

Edition Overview

| Edition | Release | Resolver | Key Features | |---------|---------|----------|--------------| | 2015 | Rust 1.0 | 1 | Original edition, mod.rs required | | 2018 | Rust 1.31 | 1 | mod.rs optional, async/await keywords, path changes | | 2021 | Rust 1.56 | 2 | Disjoint capture, IntoIterator for arrays, feature resolver | | 2024 | Rust 1.85+ | 3 | Async closures, RPIT lifetime capture, MSRV-aware resolver |

Cargo.toml Edition Configuration

[package]
name = "my-crate"
version = "0.1.0"
edition = "2024"           # Use latest edition
rust-version = "1.92"      # Project MSRV (2024 edition minimum is 1.85)

[workspace]
resolver = "3"             # Required for virtual workspaces

Edition Migration

# Check what changes are needed
cargo fix --edition --dry-run

# Apply automatic fixes
cargo fix --edition

# Update Cargo.toml manually after fixes
# edition = "2021" -> edition = "2024"

Workspace Resolver

Resolver Versions

| Version | Default For | Key Behavior | |---------|-------------|--------------| | 1 | 2015, 2018 | Basic dependency resolution | | 2 | 2021 | Feature resolver, platform-specific deps, dev-deps isolation | | 3 | 2024 | MSRV-aware, uses rust-version to select compatible deps |

Resolver 3 (Recommended)

[workspace]
members = ["crates/*"]
resolver = "3"  # MSRV-aware dependency resolution

[workspace.package]
edition = "2024"
rust-version = "1.92"
license = "MIT"
repository = "https://github.com/user/project"

Resolver 3 benefits:

  • Automatically selects dependency versions compatible with your rust-version
  • Falls back to compatible versions instead of failing
  • Reduces "dependency too new" errors

Module Organization

Modern Style (Recommended - 2018+)

src/
├── lib.rs              # Crate root
├── config.rs           # mod config
├── config/             # Submodules of config
│   ├── parser.rs       # mod config::parser
│   └── validation.rs   # mod config::validation
├── handlers.rs         # mod handlers  
└── handlers/
    ├── auth.rs         # mod handlers::auth
    └── api.rs          # mod handlers::api

In lib.rs:

pub mod config;
pub mod handlers;

In config.rs:

pub mod parser;
pub mod validation;

// Re-exports for convenience
pub use parser::Parser;
pub use validation::validate;

Legacy Style (mod.rs) - Still Valid

src/
├── lib.rs
└── config/
    ├── mod.rs          # mod config (legacy)
    ├── parser.rs
    └── validation.rs

Important: Cannot mix both styles - config.rs and config/mod.rs cannot coexist.

Private Modules

// In lib.rs
mod internal;           // Private module (no `pub`)
pub mod public_api;     // Public module

pub(crate) mod shared;  // Visible within crate only

Workspace Structure

Multi-Crate Workspace (Nebula Pattern)

project/
├── Cargo.toml              # Workspace root
├── CLAUDE.md
├── crates/
│   ├── core/
│   │   ├── Cargo.toml
│   │   └── src/lib.rs
│   ├── domain/
│   │   ├── parameter/
│   │   │   ├── Cargo.toml
│   │   │   └── src/lib.rs
│   │   └── action/
│   │       ├── Cargo.toml
│   │       └── src/lib.rs
│   └── ui/
│       ├── Cargo.toml
│       └── src/lib.rs
└── apps/
    └── cli/
        ├── Cargo.toml
        └── src/main.rs

Workspace Cargo.toml

[workspace]
members = [
    "crates/core",
    "crates/domain/*",
    "crates/ui",
    "apps/*",
]
exclude = ["experiments"]
resolver = "3"

[workspace.package]
version = "0.1.0"
edition = "2024"
rust-version = "1.92"
authors = ["Your Name <you@example.com>"]
license = "MIT"

[workspace.dependencies]
# Shared dependencies - use workspace = true in member crates
tokio = { version = "1.43", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
thiserror = "2.0"
tracing = "0.1"

# Internal crates
nebula-core = { path = "crates/core" }
nebula-parameter = { path = "crates/domain/parameter" }

Member Crate Cargo.toml

[package]
name = "nebula-parameter"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true

[dependencies]
nebula-core.workspace = true
serde.workspace = true
thiserror.workspace = true

[dev-dependencies]
tokio = { workspace = true, features = ["test-util"] }

Crate Organization Patterns

Library Crate Structure

src/
├── lib.rs              # Public API, re-exports
├── error.rs            # Error types
├── types.rs            # Core types
├── builder.rs          # Builder patterns
└── internal/           # Private implementation
    ├── mod.rs
    └── helpers.rs

lib.rs Pattern:

//! Crate-level documentation
//!
//! # Examples
//!
//! ```rust
//! use my_crate::prelude::*;
//! ```

mod error;
mod types;
mod builder;
mod internal;

// Public API
pub use error::{Error, Result};
pub use types::{Config, Options};
pub use builder::ConfigBuilder;

/// Prelude for convenient imports
pub mod prelude {
    pub use crate::{Config, ConfigBuilder, Error, Result};
}

Binary Crate Structure

src/
├── main.rs             # Entry point, minimal logic
├── cli.rs              # CLI argument parsing
├── app.rs              # Application logic
├── commands/           # Subcommands
│   ├── mod.rs
│   ├── run.rs
│   └── init.rs
└── config.rs           # Configuration

main.rs Pattern:

mod cli;
mod app;
mod commands;
mod config;

fn main() -> anyhow::Result<()> {
    let args = cli::parse();
    let config = config::load()?;
    app::run(args, config)
}

Feature Organization

Feature Flags in Cargo.toml

[features]
default = ["std"]
std = []
full = ["async", "serde", "validation"]
async = ["tokio", "async-trait"]
serde = ["dep:serde", "dep:serde_json"]
validation = ["dep:validator"]

[dependencies]
tokio = { version = "1.43", optional = true }
serde = { version = "1.0", optional = true }
serde_json = { version = "1.0", optional = true }
validator = { version = "0.18", optional = true }

Conditional Compilation

#[cfg(feature = "async")]
pub mod async_api;

#[cfg(feature = "serde")]
mod serde_impl {
    use serde::{Serialize, Deserialize};
    
    impl Serialize for crate::Config { /* ... */ }
}

#[cfg(all(feature = "async", feature = "validation"))]
pub async fn validate_async(input: &str) -> Result<(), Error> {
    // Available only with both features
}

Testing Organization

Test Structure

src/
├── lib.rs
└── parser.rs

tests/                  # Integration tests
├── common/
│   └── mod.rs          # Shared test utilities
├── integration_test.rs
└── api_tests.rs

Test Modules in Source

// In src/parser.rs
pub fn parse(input: &str) -> Result<Ast, Error> {
    // implementation
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_parse_empty() {
        assert!(parse("").is_err());
    }
    
    #[test]
    fn test_parse_valid() {
        let result = parse("valid input").unwrap();
        assert_eq!(result.nodes.len(), 1);
    }
}

Integration Tests

// tests/integration_test.rs
use my_crate::prelude::*;

mod common;

#[test]
fn test_full_workflow() {
    let config = common::setup();
    // test implementation
}

Documentation Structure

Crate Documentation

// src/lib.rs

//! # My Crate
//!
//! Brief description of what this crate does.
//!
//! ## Features
//!
//! - Feature 1
//! - Feature 2
//!
//! ## Quick Start
//!
//! ```rust
//! use my_crate::Config;
//!
//! let config = Config::builder()
//!     .option("value")
//!     .build()?;
//! # Ok::<(), my_crate::Error>(())
//! ```
//!
//! ## Modules
//!
//! - [`config`] - Configuration types
//! - [`parser`] - Parsing utilities

Best Practices

Module Guidelines

  1. One concept per module - Don't mix unrelated types
  2. Shallow hierarchies - Prefer 2-3 levels max
  3. Clear public API - Use pub use re-exports
  4. Private by default - Only expose what's needed

Naming Conventions

crate-name              # kebab-case for crate names
crate_name              # snake_case for module names (Rust converts automatically)
TypeName                # UpperCamelCase for types
function_name           # snake_case for functions
CONSTANT_NAME           # SCREAMING_SNAKE_CASE for constants

Workspace Guidelines

  1. Group by domain - crates/domain/, crates/infra/
  2. Shared dependencies - Use [workspace.dependencies]
  3. Consistent metadata - Use version.workspace = true
  4. Feature flags - Coordinate across workspace

Nebula-Specific Conventions

nebula/
├── Cargo.toml                  # Workspace root, resolver = "3"
├── CLAUDE.md                   # Project guidelines
├── crates/
│   ├── nebula-core/            # Identifiers, scope
│   ├── nebula-value/           # Runtime types
│   ├── nebula-parameter/       # Parameter definitions
│   ├── nebula-action/          # Action execution
│   ├── nebula-expression/      # Expression evaluation
│   └── nebula-ui/              # UI framework
└── apps/
    └── nebula-app/             # Main application
  • Each crate has own error type (no shared nebula-error)
  • Use thiserror for error definitions
  • Prelude pattern for common re-exports
  • Modern module style (no mod.rs)