Rust Project Organization and Multi-Binary Projects

About This Module

This module covers advanced Rust project organization, focusing on how to structure projects with multiple binaries and libraries. Students will learn about Rust's package system, understand the relationship between packages, crates, and modules, and gain hands-on experience organizing complex projects. The module also discusses best practices for managing external dependencies and the trade-offs involved in using third-party crates.

Prework

Before this lecture, please read:

Pre-lecture Reflections

  1. What are the conventional file locations for binary and library crates in a Rust project?
  2. How does Rust's module system help organize large projects?
  3. What are the security and maintenance implications of depending on external crates?

Learning Objectives

By the end of this lecture, you should be able to:

  • Organize Rust projects with multiple binaries and libraries
  • Understand the Rust module system hierarchy (packages → crates → modules)
  • Configure Cargo.toml for complex project structures
  • Evaluate external dependencies for trustworthiness and stability
  • Apply best practices for project organization and dependency management

Using Multiple Libraries or Binaries in your Project

  • So far, we went from a single source file, to multiple source files organized as Modules.

  • But we built our projects into single binaries with cargo build or cargo run.

  • We can also build multiple binaries.

When we create a new program with cargo new my_program, it creates a folder

.
├── Cargo.toml
└── src
    └── main.rs

And Cargo.toml has:

[package]
name = "my_program"
version = "0.1.0"
edition = "2024"

[dependencies]

Our program is considered a Rust package with the source in src/main.rs that compiles (cargo build) into a single binary at target/debug/my_program.

The Rust Module System

  • Packages: Cargo's way of organizing, building, testing, and sharing crates
    • It's a bundle of one or more crates.
  • Crates: A tree of modules that produces a library or executable
  • Modules and use: Let you control the organization, scope, and privacy of paths
  • Paths: A way of naming an item, such as a struct, function, or module, e.g. my_library::library1::my_function

A package can contain as many binary crates as you want, but only one library crate.

By default src/main.rs is the crate root of a binary crate with the same name as the package (e.g. my_program).

Also by default, src/lib.rs would contain a library crate with the same name as the package and src/lib.rs is its crate root.

How to add multiple binaries to your project

[[bin]]  
name = "some_name"  
path = "some_directory/some_file.rs"  

The file some_file.rs must contain a fn main()

How to add a library to your project

[lib]  
name = "some_name"  
path = "src/lib/lib.rs"  

The file lib.rs does not need to contain a fn main()

You can have as many binaries are you want in a project but only one library!

Example: simple_package

Create a new project with cargo new simple_package.

Copy the code below so your has the same structure and contents.

  • Try cargo run.
  • Since there are two binaries, you can try cargo run --bin first_bin or cargo run --bin second_bin.
.
├── Cargo.lock
├── Cargo.toml
└── src
    ├── bin
    │   └── other.rs
    ├── lib
    │   ├── bar.rs
    │   ├── foo.rs
    │   └── lib.rs
    └── main.rs

Cargo.toml:

[package]
name = "simple_library2"
version = "0.1.0"
edition = "2021"

# the double brackets indicate this is part of an array of bins
[[bin]]
name = "first_bin"
path = "src/main.rs"

[[bin]]
name = "second_bin"
path = "src/bin/other.rs"

# single bracket means we can have only one lib
[lib]
name = "librs"
path = "src/lib/lib.rs"

[dependencies]

src/bin/other.rs:

use librs;

fn main() {
   println!("This is other.rs using a library");
   librs::bar::say_something();
}

src/lib/bar.rs:

use crate::foo;

pub fn say_something() {
  println!("Bar");
  foo::say_something();
}

src/lib/foo.rs:

pub fn say_something() {
   println!("Foo");
}

src/lib/lib.rs:

/*
 * This will make the compiler look for either:
 * 1. src/lib/bar.rs or 
 * 2. src/lib/bar/mod.rs
 */
pub mod bar;

/*
 * This will make the compiler look for either:
 * 1. src/lib/foo.rs or
 * 2. src/lib/foo/mod.rs
 */
pub mod foo;

src/main.rs:

use librs;

fn main() {
   println!("This is main.rs using a library");
   librs::bar::say_something();
}

Relying on external projects

Things to consider about external libraries:

  • trustworthy?
  • stable?
  • long–term survival?
  • do you really need it?

Many things best left to professionals:

Never implement your own cryptography!

Implementing your own things can be a great educational experience!

Extreme example

Yanking a published module version: article about left-pad

article about left-pad

Rust and cargo: can't delete libraries that were published.