How to Build Multiple Binaries with Cargo Without Losing Performance?
\n\nHave you ever encountered a situation where your Rust project grows to the point where a single binary file is no longer enough? Perhaps you are writing a CLI utility that needs separate executable files for different tasks: one for data analysis, another for report generation, and a third for interacting with an API. Or you are creating a server application where you need to separate monitoring, request processing, and background tasks.
\n\nThe question "How can I build multiple binaries with Cargo?" is one of the most popular on Stack Overflow among Rust developers. And this is no coincidence. When a project goes beyond a simple "Hello, World!", the ability to properly organize the build of multiple binaries becomes a critical skill. But how to do this without losing performance? How not to drown in code duplication and not slow down compilation?
\n\nIn this article, we will not just answer this question — we will show how to turn building multiple binaries from a headache into a powerful optimization tool. You will learn about best practices, performance, and even secret Cargo tricks used by experienced Rust developers.
\n\nWhy Multiple Binaries? The Problem of a Single Executable File
\n\nImagine: you wrote an excellent library for working with data. Then you added a CLI interface. Then a web server. Then a task scheduler. All in one project. The result? A monolith that:
\n\n- \n
- Compiles forever, even if you change only one line in the CLI. \n
- Requires restarting the entire application when updating one component. \n
- Is difficult to test and deploy. \n
The solution is to split the project into several binaries. But how to do this correctly in Rust?
\n\nBasics: Cargo.toml and the [[bin]] Section
\n\nThe simplest way is to declare multiple binary targets in your Cargo.toml. Let's break down a basic example.
// Cargo.toml\n[package]\nname = "my_project"\nversion = "0.1.0"\nedition = "2021"\n\n[[bin]]\nname = "analyzer"\npath = "src/bin/analyzer.rs"\n\n[[bin]]\nname = "reporter"\npath = "src/bin/reporter.rs"\n\n[[bin]]\nname = "server"\npath = "src/bin/server.rs"\n\n[dependencies]\nserde = { version = "1.0", features = ["derive"] }\ntokio = { version = "1", features = ["full"] }\n\n\nNow in the src/bin/ folder you have three files: analyzer.rs, reporter.rs, and server.rs. Each of them is a full-fledged binary with its own main() function.
To build everything at once:
\ncargo build --release\n\nOr a specific binary:
\ncargo build --release --bin analyzer\n\nSimple? Yes. But that's where the catch lies. If you don't optimize the build, each binary will be compiled from scratch, even if they use the same dependencies. And that is a direct path to performance loss.
\n\nBuild Performance Optimization: Cargo Profiles
\n\nThe secret to efficiently building multiple binaries lies in Cargo profiles. This is a powerful tool that allows you to configure compilation parameters for different scenarios.
\n\nBasic Profiles: dev and release
\n\nBy default, Cargo uses two profiles: dev (for development) and release (for production). But you can create your own profiles or modify existing ones.
// Cargo.toml\n[profile.release]\nopt-level = 3 # Maximum code optimization\nlto = "fat" # Link-Time Optimization (merging all crates)\ncodegen-units = 1 # Single codegen unit for better optimization\nstrip = true # Remove debug information\n\n\nThese settings are especially important for binaries that need to run fast. But there is a nuance: the more aggres