An alternate solution is to not actually try to cram both things into one package. For slightly larger projects with a friendly executable, I've found it very nice to use a workspace
We create a binary project that includes a library inside of it:
the-binary
├── Cargo.lock
├── Cargo.toml
├── mylibrary
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
└── src
└── main.rs
Cargo.toml
This uses the [workspace] key and depends on the library:
[package]
name = "the-binary"
version = "0.1.0"
authors = ["An Devloper <[email protected]>"]
[workspace]
[dependencies]
mylibrary = { path = "mylibrary" }
src/main.rs
extern crate mylibrary;
fn main() {
println!("I'm using the library: {:?}", mylibrary::really_complicated_code(1, 2));
}
mylibrary/src/lib.rs
use std::error::Error;
pub fn really_complicated_code(a: u8, b: u8) -> Result<u8, Box<Error>> {
Ok(a + b)
}
And execute it:
$ cargo run
Compiling mylibrary v0.1.0 (file:///private/tmp/the-binary/mylibrary)
Compiling the-binary v0.1.0 (file:///private/tmp/the-binary)
Finished dev [unoptimized + debuginfo] target(s) in 0.73 secs
Running `target/debug/the-binary`
I'm using the library: Ok(3)
There are two big benefits to this scheme:
The binary can now use dependencies that only apply to it. For example, you can include lots of crates to improve the user experience, such as command line parsers or terminal formatting. None of these will "infect" the library.
The workspace prevents redundant builds of each component. If we run cargo build in both the mylibrary and the-binary directory, the library will not be built both times — it's shared between both projects.