Monorepo with Rust: Code Organization for Beginners

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: Why Do You Need a Monorepo in Rust?



When you start learning Rust, the first thing you encounter is a single project created via cargo new. But what do you do when your project grows: shared libraries appear, multiple executable files (e.g., a server and a client), or you want to extract repetitive code into a separate package?



This is where the monorepo approach comes to the rescue — it is an approach where the code of several interconnected projects (libraries and executables) is stored in a single repository. Rust provides a built-in solution for this — Cargo Workspaces.



In this article, we will break down how to organize a monorepo in Rust from scratch: starting from the folder structure to practical examples. You will learn how to properly split code into modules, connect local dependencies, and manage the build.



1. What is a Cargo Workspace and How to Create It?



A workspace is a set of crates that share the same Cargo.lock file and a common target directory. This helps avoid dependency version conflicts and speeds up compilation.



Creating the Monorepo Structure



Let's create a simple monorepo with two crates: a library utils and an executable app.



mkdir my_monorepocd my_monorepocargo new utils --libcargo new app


Now, let's create the root Cargo.toml file that declares the workspace:



[workspace]members = [    "utils",    "app",]


Important: Make sure that in the utils/Cargo.toml and app/Cargo.toml files there is no [package] section with a workspace field — it is not needed. Cargo will understand that this is part of the workspace.



Project Structure



After creation, you will have the following structure:



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


2. Code Organization: Libraries and Modules



Now let's add code to the utils library. Suppose we want to create common functions for working with numbers and strings.



Library Code (utils/src/lib.rs)



pub mod math;pub mod strings;


Let's create the module files:



// utils/src/math.rspub fn add(a: i32, b: i32) -> i32 {    a + b}

pub fn multiply(a: i32, b: i32) -> i32 { a * b}


// utils/src/strings.rspub fn greet(name: &str) -> String {    format!("Hello, {}!", name)}

pub fn to_uppercase(text: &str) -> String { text.to_uppercase()}


Connecting the Library in the Executable File



In the app/Cargo.toml file, specify the dependency on the local crate:



[package]name = "app"version = "0.1.0"edition = "2021"

[dependencies]utils = { path = "../utils" }


Now in app/src/main.rs we can use functions from utils:



use utils::math;use utils::strings;

fn main() { let sum = math::add(5, 10); println!("5 + 10 = {}", sum);

let greeting = strings::greet("World"); println!("{}", greeting);

let upper = strings::to_uppercase("rust rocks"); println!("{}", upper);}


Run the project from the root of the monorepo with the command:



cargo run -p app


You will see the output:



5 + 10 = 15Hello, World!RUST ROCKS


3. Advanced Organization: Multiple Executable Files



Often in a monorepo, you need to have multiple

Blogs

Book Recommendations