Adding an executable target to a Rust library

Using Cargo, we can easily create a library project with the new command:

cargo new pumpkin-rs --lib

Of course, the primary purpose of libraries is to encapsulate some kind of functionality that can be then exported for use by other libraries or applications.

For the purpose of this post, let’s define a simple function in src/lib.rs which will be the essence of our library. Later we will see how we can use this function from a runnable binary target in the same project.

pub fn say_hello() {
    println!("Hello from the library");
}

You may run cargo build just to double-check everything builds successfully… and voilà!

Cargo Targets

In certain situations, it may be helpful to have a library project include a runnable component - a stand-alone, runnable program that can be executed.

Cargo targets allow us to define different combinations of source files which can be compiled in a project. Targets can specify what part of the source code is a library, binary, example, unit test, or benchmark.

The cool thing is that Cargo also uses folder structure conventions to guess which source file does what. For example, src/lib.rs is where it will look for the library code, executable code is expected in src/main.rs or src/bin/.

Add a default binary target

Following the conventions above, we can add a default executable to a library project by creating src/bin/pumpkin.rs:

use pumpkin_rs::say_hello;

fn main() {
    say_hello()
}

Running cargo run will execute the binary! 🎉

Not only that, but cargo build now also produces 2 separate outputs - we get the compiled binary for the library as well as the executable.

More than one binary targets

We are not limited to a single binary target. There can be more than one! Let’s create src/bin/tomato.rs

fn main() {
    println!("Hello 🍅")
}

Now, if we just run cargo run like before, we’d get an error message because there are two available binary targets, and cargo doesn’t know which one we want to use. The error message helpfully suggests we can use the target configuration in Cargo.toml to define a default-run key or pass the --bin argument to the run command.

cargo run --bin tomato