Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Organization

Rust organizes code through files, modules, crates, and workspaces. How you use these structures affects two things that matter as a project grows: development speed (how fast you can compile and iterate) and loose coupling (how easily you can change one part without breaking another).

Crate workspace example

Example of a Rust project’s organization, with a single workspace containing multiple crates.

Before we dive into this chapter, we should define what all of these terms mean.

NameDescription
ModuleModules in Rust are used to hierarchically split code into logical units. Modules have a path, for example std::fs. Modules contain functions, structs, traits, impl blocks, and other modules.
FileA single source file, typically with a .rs extension. Every file is a module, but files can also contain inline (nested) modules.
CrateCompilation unit in Rust. Can be a library crate or a binary crate, the latter require the presence of a main() function. They have an entrypoint, which is typically lib.rs or main.rs but can also be called something else.
PackageCollection of crates. Every package may contain at most one library crate, and may contain multiple binary crates.
WorkspaceA collection of packages, which can share a build cache, dependencies and metadata.

In this chapter, we will briefly cover how you can use these to structure your project.

Development Speed

Rust’s zero-cost abstractions produce fast binaries, but at the expense of compile times1. This tradeoff means that how you organize your project directly affects how fast you can iterate. A tight compile-test loop is essential for productive development, and the organizational choices in this chapter (splitting into crates, using workspaces, managing features) are the main levers you have to keep compile times under control as a project grows.

Loose Coupling

Large, monolithic codebases become difficult to change because everything depends on everything else. Splitting code into smaller, independent units with well-defined interfaces makes it easier to test components in isolation, assign ownership to different teams, and change implementations without cascading breakage. Rust’s module and crate system provides natural boundaries for achieving this2.

Reading

This chapter of The Rust Book shows you what facilities Rust has for structuring projects. It introduces the concepts of packages, crates and modules.

Chapter 2.5: Project Layout by The Cargo Book

This section in The Cargo Book explains the basic layout of a Rust project.

Roman discusses how you can scale Rust projects, and what he has learned from participating in several large Rust projects. He gives some guidance on when to put things into modules versus into crates, and what implication this has on compile times. He also gives some advice on programming patterns, such as preferring run-time polymorphism over compile-time polymorphism. This article is a must-read for anyone dealing with a growing Rust project and it encodes a lot of wisdom that otherwise takes a long time to acquire.

Rust compile times by Matthias Endler

Matthias covers a wide range of strategies for reducing Rust compile times, from updating your toolchain and removing unused dependencies to splitting crates, using faster linkers, and optimizing CI with caching and cargo-nextest.

Nick explores how aggressive inlining and monomorphization can unexpectedly bloat compiled artifacts. He demonstrates how a single #[inline(always)] annotation on a large function caused massive code duplication across generic instantiations, and shows how trait objects and removing inline hints reduced binary size with negligible performance impact.

Alex argues for consolidating multiple integration test files into a single test crate. Each integration test file compiles into a separate binary that must be linked independently, and Cargo runs test binaries sequentially. When the Cargo project itself consolidated its integration tests, compile time dropped 3x and on-disk artifacts shrank 5x.


  1.    Procedural macros allow for eliminating a lot of repeated code, for example
       by automatically deriving traits on structures. However, they need to be
       built and executed and thus add to the compilation time.
    
  2.        See [Loose Coupling](https://en.wikipedia.org/wiki/Loose_coupling)
           (Wikipedia).
           ````