Monorepo with Rust: Organizing Code in Practice

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

Monorepo with Rust: Organizing Code in Practice

\n\n

When developing large projects in Rust, sooner or later the question arises: how to organize code so that it remains maintainable, reusable, and easily scalable? One popular approach is the monorepo. In this article, we will break down what a monorepo is in the context of Rust, how to set it up using Cargo Workspaces, and what practical techniques will help avoid chaos in the codebase.

\n\n

The material will be useful both for beginners just starting to work with large projects and for experienced developers seeking effective ways to organize code.

\n\n

What is a Monorepo and Why is it Needed in Rust?

\n\n

A monorepo is an approach where all the code of a project (or several related projects) is stored in a single repository, as opposed to multi-repositories where each service or library lives in its own Git repository.

\n\n

Why do Rust developers often choose a monorepo?

\n
    \n
  • Single dependency version. You avoid conflicts when different crates require different versions of the same library.
  • \n
  • Simplified refactoring. Changing an API in one crate is immediately visible in all dependent crates — the Rust compiler will point out all the places that need fixing.
  • \n
  • Shared compilation. Cargo can optimize the build by reusing already compiled artifacts.
  • \n
  • Unified change history. You don't need to synchronize commits across different repositories.
  • \n
\n\n

The main tool for organizing a monorepo in Rust is Cargo Workspaces. Let's look at how to use it in practice.

\n\n

Setting Up Cargo Workspaces: A Step-by-Step Example

\n\n

Suppose we are creating an application consisting of three parts:

\n
    \n
  • the core_lib library — common logic;
  • \n
  • the utils library — utilities;
  • \n
  • the app executable — the main binary.
  • \n
\n\n

Let's create the monorepo structure:

\n\n
my_monorepo/\n├── Cargo.toml          # root workspace manifest\n├── core_lib/\n│   ├── Cargo.toml\n│   └── src/\n│       └── lib.rs\n├── utils/\n│   ├── Cargo.toml\n│   └── src/\n│       └── lib.rs\n└── app/\n    ├── Cargo.toml\n    └── src/\n        └── main.rs\n
\n\n

Step 1. Root Cargo.toml

\n\n
[workspace]\nmembers = [\n    "core_lib",\n    "utils",\n    "app",\n]\n
\n\n

Now all three crates are part of one workspace. Cargo will manage them as a single unit.

\n\n

Step 2. Internal dependencies

\n

In the app/Cargo.toml file, we specify that it depends on our libraries:

\n\n
[package]\nname = "app"\nversion = "0.1.0"\nedition = "2021"\n\n[dependencies]\ncore_lib = { path = "../core_lib" }\nutils = { path = "../utils" }\n
\n\n

Step 3. Writing code

\n

Example code in core_lib/src/lib.rs:

\n\n
pub fn greet(name: &str) -> String {\n    format!("Hello, {}!", name)\n}\n
\n\n

In app/src/main.rs we use this function:

\n\n
use core_lib::greet;\n\nfn main() {\n    println!("{}", greet("Rust Developer"));\n}\n
\n\n

Step 4. Building and running

\n

The commands cargo build, cargo test, and cargo run work for the entire workspace. You can also run a specific crate:

\n\n
cargo run -p app\ncargo test -p core_lib\n
\n\n

This is the basic setup. Now let's move on to more advanced practices.

\n\n

Advanced Organization: Modules, Tests, and Versioning

\n\n

When there are more crates, it's important to maintain discipline. Here are a few

Blogs

Book Recommendations