Essay · · 1 min

Rust Project Layout

How a Rust project should be laid out: crates, binaries, tests, benches and the few conventions that keep a growing codebase legible.

By Sonami

Rust Project Layout Guide

Basic Project Structure

my_project/
|-- Cargo.toml          # Project manifest and dependencies
|-- Cargo.lock          # Locked dependency versions (auto-generated)
|-- src/                # Source code directory
|   |-- main.rs         # Entry point for binary crates
|   `-- lib.rs          # Entry point for library crates
|-- tests/              # Integration tests
|-- benches/            # Benchmarks
|-- examples/           # Example programs
|-- target/             # Build artifacts (gitignored)
`-- README.md           # Project documentation

Binary vs Library Projects

Binary Crate (Application)

my_app/
|-- Cargo.toml
`-- src/
    |-- main.rs         # Required entry point
    `-- bin/            # Additional binaries (optional)
        `-- other_tool.rs

Library Crate

my_lib/
|-- Cargo.toml
`-- src/
    `-- lib.rs          # Required entry point

Mixed Binary and Library

my_project/
|-- Cargo.toml
`-- src/
    |-- main.rs         # Binary entry point
    `-- lib.rs          # Library entry point

Module Organization

Simple Module Structure

src/
|-- main.rs
|-- config.rs           # Single-file module
`-- utils.rs            # Single-file module

Nested Module Structure

src/
|-- main.rs
|-- lib.rs
|-- models/             # Module directory
|   |-- mod.rs          # Module declaration
|   |-- user.rs
|   `-- post.rs
|-- handlers/
|   |-- mod.rs
|   |-- auth.rs
|   `-- api.rs
`-- utils/
    |-- mod.rs
    |-- validation.rs
    `-- helpers.rs

Common Project Layouts

Web Application (using Actix-web/Rocket)

web_app/
|-- Cargo.toml
|-- .env                # Environment variables
|-- migrations/         # Database migrations
|-- static/             # Static assets
|   |-- css/
|   |-- js/
|   `-- images/
|-- templates/          # HTML templates
|-- src/
|   |-- main.rs
|   |-- lib.rs
|   |-- config.rs       # Configuration
|   |-- db.rs           # Database connection
|   |-- models/         # Data models
|   |   |-- mod.rs
|   |   `-- user.rs
|   |-- handlers/       # Request handlers
|   |   |-- mod.rs
|   |   `-- users.rs
|   |-- middleware/     # Middleware
|   |   |-- mod.rs
|   |   `-- auth.rs
|   `-- routes.rs       # Route definitions
`-- tests/
    `-- integration_test.rs

CLI Application

cli_app/
|-- Cargo.toml
|-- src/
|   |-- main.rs         # Entry point and CLI setup
|   |-- cli.rs          # CLI argument parsing
|   |-- commands/       # Command implementations
|   |   |-- mod.rs
|   |   |-- init.rs
|   |   |-- build.rs
|   |   `-- run.rs
|   |-- config/         # Configuration management
|   |   |-- mod.rs
|   |   `-- settings.rs
|   `-- utils/
|       |-- mod.rs
|       `-- helpers.rs
`-- tests/
    `-- cli_test.rs

Library with Features

my_library/
|-- Cargo.toml          # With feature flags defined
|-- src/
|   |-- lib.rs          # Main library interface
|   |-- core/           # Core functionality
|   |   |-- mod.rs
|   |   `-- engine.rs
|   |-- features/       # Optional features
|   |   |-- mod.rs
|   |   |-- async_support.rs
|   |   `-- serialization.rs
|   `-- prelude.rs      # Common re-exports
|-- examples/
|   |-- basic.rs
|   `-- advanced.rs
|-- benches/
|   `-- performance.rs
`-- tests/
    |-- unit_tests.rs
    `-- integration_tests.rs

Workspace Layout (Multiple Crates)

workspace/
|-- Cargo.toml          # Workspace manifest
|-- crates/
|   |-- core/           # Core library
|   |   |-- Cargo.toml
|   |   `-- src/
|   |       `-- lib.rs
|   |-- cli/            # CLI binary
|   |   |-- Cargo.toml
|   |   `-- src/
|   |       `-- main.rs
|   `-- api/            # API server
|       |-- Cargo.toml
|       `-- src/
|           `-- main.rs
|-- shared/             # Shared utilities
|   |-- Cargo.toml
|   `-- src/
|       `-- lib.rs
`-- docs/               # Documentation

Workspace Cargo.toml

[workspace]
members = [
    "crates/core",
    "crates/cli",
    "crates/api",
    "shared",
]

[workspace.dependencies]
serde = "1.0"
tokio = "1.0"

Testing Structure

Unit Tests (inline)

// src/calculator.rs
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

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

    #[test]
    fn test_add() {
        assert_eq!(add(2, 2), 4);
    }
}

Integration Tests

tests/
|-- common/             # Shared test utilities
|   `-- mod.rs
|-- integration_test1.rs
`-- integration_test2.rs

Configuration Files

Essential Configuration

my_project/
|-- .gitignore          # Git ignore patterns
|-- .cargo/
|   `-- config.toml     # Cargo configuration
|-- rust-toolchain.toml # Rust version specification
|-- rustfmt.toml        # Code formatting rules
|-- clippy.toml         # Linter configuration
|-- .github/
|   `-- workflows/
|       `-- ci.yml      # CI/CD pipeline
`-- .vscode/            # VS Code settings
    `-- settings.json

Example .gitignore

# Rust
/target
**/*.rs.bk
*.pdb
Cargo.lock  # For libraries

# IDE
.idea/
*.swp
*.swo
.vscode/
*.code-workspace

# OS
.DS_Store
Thumbs.db

# Environment
.env
*.env.local

Example rustfmt.toml

edition = "2021"
max_width = 100
use_small_heuristics = "Max"
imports_granularity = "Crate"
group_imports = "StdExternalCrate"

Documentation Structure

my_project/
|-- README.md           # Project overview
|-- CHANGELOG.md        # Version history
|-- CONTRIBUTING.md     # Contribution guidelines
|-- LICENSE             # License file
|-- docs/
|   |-- architecture.md
|   |-- api.md
|   `-- guides/
|       |-- getting_started.md
|       `-- development.md
`-- src/
    `-- lib.rs          # Doc comments for API docs

Build Scripts and Assets

my_project/
|-- build.rs            # Build script
|-- assets/             # Runtime assets
|   |-- config/
|   |   `-- default.toml
|   `-- data/
|       `-- init.sql
`-- resources/          # Embedded resources
    `-- schema.sql

Example build.rs

use std::env;
use std::fs;
use std::path::Path;

fn main() {
    // Copy assets to output directory
    let out_dir = env::var("OUT_DIR").unwrap();
    let dest_path = Path::new(&out_dir).join("config.toml");
    fs::copy("assets/config/default.toml", dest_path).unwrap();
    
    // Rerun if changed
    println!("cargo:rerun-if-changed=assets/");
}

Best Practices

1. Module Organization

  • Keep modules focused on a single responsibility
  • Use mod.rs for module directories
  • Prefer flat module structures when possible
  • Group related functionality together

2. Naming Conventions

  • Use snake_case for file and directory names
  • Match module names to file names
  • Use descriptive names that indicate purpose

3. Separation of Concerns

src/
|-- domain/         # Business logic
|-- application/    # Application services
|-- infrastructure/ # External services
`-- presentation/   # API/CLI layer

4. Feature Flags

[features]
default = ["std"]
std = []
async = ["tokio", "async-trait"]
serde_support = ["serde", "serde_json"]

5. Dependencies Organization

[dependencies]
# Core dependencies
serde = { version = "1.0", features = ["derive"] }

# Optional dependencies
tokio = { version = "1.0", optional = true }

[dev-dependencies]
# Test dependencies
criterion = "0.5"
mockall = "0.11"

[build-dependencies]
# Build script dependencies
cc = "1.0"

Platform-Specific Code

src/
|-- lib.rs
|-- common.rs           # Shared code
`-- platform/
    |-- mod.rs
    |-- unix.rs         # Unix-specific
    |-- windows.rs      # Windows-specific
    `-- wasm.rs         # WebAssembly-specific

FFI (Foreign Function Interface) Layout

ffi_project/
|-- Cargo.toml
|-- build.rs            # Generate bindings
|-- src/
|   |-- lib.rs
|   |-- ffi.rs          # FFI definitions
|   `-- wrapper.rs      # Safe Rust wrappers
|-- include/            # C headers
|   `-- mylib.h
`-- cbindgen.toml       # C binding generator config

Macro Projects

macro_lib/
|-- Cargo.toml
|-- src/
|   `-- lib.rs          # Macro definitions
|-- macro_derive/       # Procedural macros
|   |-- Cargo.toml
|   `-- src/
|       `-- lib.rs
`-- tests/
    `-- macro_tests.rs

Also