Monorepo with Rust: Code Organization for Large Projects

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction

As a Rust project grows, the question arises: how to organize code so that it remains maintainable, scalable, and convenient for the team? One popular approach is a monorepository (monorepo). In this article, we will break down what a monorepository is, how to organize it using Cargo workspace, and provide practical examples. You will learn how to structure code, manage dependencies, and avoid common mistakes.



What is a monorepository and why is it needed in Rust?

A monorepository (or monorepo) is a single repository that contains multiple projects (crates) or packages. Unlike multi-repositories, where each project lives separately, a monorepository combines everything: libraries, binaries, tests, configurations, and documentation.

Advantages of a monorepository for Rust:

  • Single dependency version — all crates use the same versions of external packages, which reduces conflicts.
  • Simplified refactoring — changes in one library are immediately visible in all dependent crates.
  • End-to-end integration — you can easily run tests and linting for the entire project.
  • Centralized documentation — all documents and CI/CD are in one place.

However, a monorepository is not suitable for everyone. For small projects or teams with different development cycles, it is better to choose a multi-repo. But for the Rust ecosystem, where modularity and code reuse are the norm, a monorepository becomes a powerful tool.



Organizing a monorepository with Cargo Workspace

Cargo supports the workspace mechanism — this is a group of crates that are built together and share a common Cargo.lock. Let's look at the basic structure.



Creating a workspace

Create a root directory and a Cargo.toml file without a [package] section, but with a [workspace] section:

# root Cargo.toml[workspace]members = [    "core",    "cli",    "web",]

Now create the subdirectories core, cli, web. Each of them must have its own Cargo.toml with a typical package structure. For example, for core:

# core/Cargo.toml[package]name = "my_core"version = "0.1.0"edition = "2021"

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

For cli and web, you can specify a dependency on my_core:

# cli/Cargo.toml[package]name = "my_cli"version = "0.1.0"edition = "2021"

[dependencies]my_core = { path = "../core" }clap = { version = "4.0", features = ["derive"] }

Now when you run cargo build from the root, all crates will be built, and Cargo.lock will be shared.



Folder and module structure

Recommended monorepository structure for Rust:

my_monorepo/├── Cargo.toml          # workspace├── Cargo.lock├── core/               # library with common types│   ├── Cargo.toml│   └── src/│       └── lib.rs├── cli/                # console application│   ├── Cargo.toml│   └── src/│       └── main.rs├── web/                # web server (e.g., on Actix)│   ├── Cargo.toml│   └── src/│       └── main.rs├── tests/              # integration tests (optional)│   └── integration_test.rs├── scripts/            # scripts for CI/CD└── README.md

Inside each crate, use the standard Rust module structure. For example, in core/src/lib.rs:

// core/src/lib.rspub mod models;pub mod utils;

And in core/src/models.rs:

// core/src/models.rsuse serde::{Serialize, Deserialize};

#[derive(Debug, Serialize, Deserialize)]pub struct

Blogs

Book Recommendations