Library and Binary in Rust: How to Package Without Losing Performance

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

One Package — Two Entities: Myth or Reality?

\n\n

Imagine: you've written an amazing Rust library for working with big data. It's lightning-fast, safe, and elegant. But you need not only an API for other developers, but also a ready-made CLI utility to test the library or use it in pipelines. A classic question arises, one that has gathered thousands of views on Stack Overflow: "Package with both a library and a binary?"

\n\n

For a Rust beginner, this sounds like a puzzle. For an experienced developer, it's a task of optimizing project structure and performance. In this article, we won't just answer this question — we'll do it with a focus on optimization and performance — the two things Rust is most loved for.

\n\n

We'll break down how to properly organize such a package, avoid code duplication, speed up compilation, and make your binary truly fast. Let's go!

\n\n

Package Structure: Cargo.toml Decides Everything

\n\n

In Rust, a single Cargo package can contain both a library (lib) and multiple binaries (bin). This isn't magic, but a simple folder structure convention. Let's look at a minimal example.

\n\n

Create a project:

\n\n
cargo new my_tool --lib
\n\n

Now modify the Cargo.toml file to explicitly specify that we have both a library and a binary:

\n\n
[package]\nname = "my_tool"\nversion = "0.1.0"\nedition = "2021"\n\n[lib]\nname = "my_tool_lib"\npath = "src/lib.rs"\n\n[[bin]]\nname = "my_tool"\npath = "src/main.rs"\n\n[dependencies]\n
\n\n

The folder structure will look like this:

\n\n
my_tool/\n├── Cargo.toml\n├── src/\n│   ├── lib.rs      // Library\n│   └── main.rs     // Binary\n
\n\n

Simple, right? But the devil is in the details. If you just copy the code from the library into main.rs, you'll get duplication and performance issues. You need to be smarter.

\n\n

The Right Approach: Binary Uses the Library

\n\n

The binary should import the library as an external crate. Even if they are in the same package. This not only reduces duplication but also allows the Rust compiler to better optimize code through cross-crate optimization (LTO).

\n\n

Example src/lib.rs:

\n\n
pub fn calculate(data: &[u64]) -> u64 {\n    data.iter().sum()\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_calculate() {\n        assert_eq!(calculate(&[1, 2, 3]), 6);\n    }\n}\n
\n\n

Example src/main.rs:

\n\n
use my_tool_lib::calculate;\n\nfn main() {\n    let numbers = vec![10, 20, 30];\n    println!("Sum: {}", calculate(&numbers));\n}\n
\n\n

Notice: we use my_tool_lib — this is the name we set for the library in Cargo.toml. The compiler will automatically link them into one binary, while maintaining modularity.

\n\n

Performance Optimization: LTO, Inline, and Benchmarks

\n\n

Now that the structure is ready, let's talk about the main thing — how to squeeze the maximum out of your package. After all, if you're creating a data processing tool, every microsecond counts.

\n\n

1. Enable Link-Time Optimization (LTO)

\n\n

LTO allows the compiler to optimize code across crate boundaries. Without LTO, functions from the library and binary are optimized separately. With LTO — as a single unit.

\n\n

Add to Cargo.toml:

\n\n
[profile.release]\nlto = true\ncodegen-units = 1\n
\n\n

This will increase compilation time, but will make the binary noticeably faster. For the educational site pythonlib.ru, it's important to emphasize: LTO is the standard for production builds in Rust.

\n\n

2. Use Inline Hints

\n\n

If there are critically important functions in the library,

Blogs

Book Recommendations