---
title: "13.5 Binary and library crates"
description: "The difference between a binary crate (main.rs) and a library crate (lib.rs), having both in one package, and why the split matters for testing and reuse."
url: "https://learnrust.net/chapter-13/binary-and-library-crates/"
last_updated: "2026-06-13"
---

# 13.5 Binary and library crates

You've been saying "crate" since lesson [7.9](https://learnrust.net/chapter-7/adding-a-dependency-random-numbers/), when `cargo add rand` pulled one in. This lesson pins the word down and draws a distinction that shapes how real Rust projects are laid out: a **crate** comes in two kinds, binary and library, and a single package often has both. Getting this split right is what makes your code testable and reusable, which is why the testing chapter (14) depends on it.

## Crate, package, the vocabulary

A **crate** is the unit the Rust compiler compiles at once: a tree of modules (lessons [13.1](https://learnrust.net/chapter-13/naming-collisions-and-modules/) through [13.4](https://learnrust.net/chapter-13/splitting-code-into-files/)) with a single root file. A **package** is what `cargo new` creates: a `Cargo.toml` plus one or more crates. Most packages you've made so far were one binary crate, but the package is the thing you publish and the crate is the thing that compiles.

There are two kinds of crate:

A **binary crate** compiles to an executable you can run. Its root is `src/main.rs`, and it must have a `fn main` (lesson [1.1](https://learnrust.net/chapter-1/statements-and-program-structure/)). Every program in the course so far has been a binary crate; `cargo run` builds and runs it.

A **library crate** has no `main` and compiles to nothing you run directly. Instead it provides code for *other* crates to use. Its root is `src/lib.rs`. The `rand` crate you added is a library crate: you don't run `rand`, you call into it.

## One package, both crates

Here's the layout that matters. A single package can contain *both* a library crate and a binary crate: `src/lib.rs` holds the real logic, and `src/main.rs` is a thin wrapper that calls into the library.

```
my_tool/
├── Cargo.toml
└── src/
    ├── lib.rs      ← the library crate: all the logic
    └── main.rs     ← the binary crate: a thin shell calling the library
```

```rust
// src/lib.rs
pub fn run(input: &str) -> usize {
    input.split_whitespace().count()
}
```

```rust
// src/main.rs
use my_tool::run;

fn main() {
    let count = run("the quick brown fox");
    println!("{count} words");
}
```

```
4 words
```

Notice `use my_tool::run;` in `main.rs`. The binary refers to the library *by the package's name* (`my_tool`, set in `Cargo.toml`), exactly as it would refer to any external dependency. From the binary's point of view, your own library is just another crate it depends on, reached through its public API. Anything the binary needs from the library must therefore be `pub` (lesson [13.3](https://learnrust.net/chapter-13/visibility-pub/)).

> **Key insight**
>
> Put the logic in `lib.rs` and keep `main.rs` thin. The binary should do little more than gather inputs (command-line arguments, files), hand them to library functions, and report results. Everything worth testing lives in the library, where tests can reach it through the public API. A fat `main.rs` full of logic is hard to test, because `main` itself isn't easily callable; a thin `main.rs` over a real library is the standard shape of a well-built Rust tool.

## Why the split matters for testing

This is the IOU to chapter 14. Rust's integration tests (lesson [14.4](https://learnrust.net/chapter-14/integration-tests/)) live in a `tests/` directory and can only see a crate's *public API*, the same way an outside user would. They can test a library crate, because a library *has* a public API. They can't meaningfully test a binary crate, because a binary exposes only an executable, not callable functions.

So the reason to split logic into `lib.rs` isn't bureaucratic. It's that the library is the part you can test thoroughly: unit tests inside it (lesson [14.3](https://learnrust.net/chapter-14/organizing-unit-tests/)) and integration tests against its public surface (lesson [14.4](https://learnrust.net/chapter-14/integration-tests/)). The binary becomes a shell so small there's almost nothing left in it to break. When chapter 20 builds a real command-line tool, this `lib.rs`-plus-thin-`main.rs` structure is exactly the shape it uses, and the testability is the payoff.

## Multiple binaries

One more layout you'll meet: a package can ship several binaries by putting extra ones in `src/bin/`. Each file there (`src/bin/tool_a.rs`, `src/bin/tool_b.rs`) becomes its own binary crate, runnable with `cargo run --bin tool_a`. This is handy for a project that offers a few related command-line tools sharing one library. You won't need it often, but it explains why `cargo run` sometimes asks *which* binary you mean.

## Quiz time

**Question #1**

What's the difference between a binary crate and a library crate, and what is each one's root file?

<details class="solution">
<summary>Show solution</summary>

A binary crate compiles to an executable and must have a `fn main`; its root is `src/main.rs`. A library crate has no `main`, compiles to code for other crates to use, and its root is `src/lib.rs`. A single package can contain both.

</details>

**Question #2**

Why is it good practice to put a program's logic in `lib.rs` and keep `main.rs` thin?

<details class="solution">
<summary>Show solution</summary>

Because the library crate has a public API that tests (especially integration tests in `tests/`) can call, while a binary exposes only an executable that's hard to test. Putting logic in the library makes it thoroughly testable; keeping `main.rs` to input-gathering and reporting leaves almost nothing untestable in the binary. It also makes the logic reusable by other crates.

</details>

**Question #3**

In the two-crate example, why must `run` be marked `pub`?

<details class="solution">
<summary>Show solution</summary>

Because `main.rs` is a *separate crate* that depends on the library crate, and it reaches `run` through the library's public API (`use my_tool::run;`). Items are private by default (lesson [13.3](https://learnrust.net/chapter-13/visibility-pub/)), so a private `run` would be invisible to the binary, just as it would be to any external user of the library.

</details>

You now understand crates as compile units and the library/binary split. The next lesson surveys the tool that orchestrates it all, `Cargo`: build profiles, `cargo doc`, a glance at workspaces and features.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
