Trim Those Packages

2025-10-02

Cargo is an excellent tool that makes it easy to publish your code and share it with others. However, it can also lead to publishing packages that are much larger than necessary if you're not careful. We can make fun of JavaScript for including Guy Fieri in its packages, but Cargo also makes it easy to do similar things. Default behavior of Rust package manager for packaging boils down to including all files that are not part of .gitignore and this can lead to unnecessary bloat. Some projects may include a fancy gif that is used only in a README file, some include binary assets that are used by some of the examples. Reasons differ, but the result is the same: a package that is much larger than necessary.

I don't need or want to shout at any specific crate, but I will show you how you can inspect a bit the bottomless hole, or how the nerds call it, the ~/.cargo/registry/cache directory. To do so you need a steady hand and a terminal with Unix tools. Below is the command to list locally cached packages, get their sizes, and sort them:

find ~/.cargo/registry/cache -iname "*.crate" -print0 | xargs -0 du -h | sort -h

I will not call out the size of any of the crates, because it is not the point of this article. The point is to show you how to trim your packages so they are small and efficient.

Specifying package contents

Cargo publish by default has a rather opt-out approach for deciding what to include in the package (which is IMHO wrong approach, but it is what it is). It uses the .gitignore file if present to exclude files or just exclude anything that starts with .. One more rule would be to exclude other packages (if you have a subdirectory with Cargo.toml in it it will be excluded).

But even with not so ideal default configuration it can be changed to opt-in approach by specifying the include field in the Cargo.toml package section. It should be a list of patterns that match the files and directories you want to include in the package. Example:

[package]
# name of the package, etc.
# ...
include = ["/src", "README.md"]

There is also exclude field which can be used to exclude files or directories from the package. You need to specify only one of them. I would suggest using the include approach, since I preffer opt-in approach. You can learn more about both of the fields by reading Rust documentation.

Inspecting the package contents

Cargo also gives you a way to get information what will be included in the package:

cargo package --list --allow-dirty

You can, and should, call these commands to package the project without deploying it to the registry (extra command for getting the size of the package):

cargo publish --dry-run --allow-dirty
du -h target/package/*.crate

Argument --allow-dirty is used to allow to use the commands in repositories that have uncommitted changes.

It should be used to verify that the package will include only the files and directories you want.

Example

I have an old package for Bevy that I have not updated for a long time, but it can serve as an example of how not to do it (and how to fix it). You can clone the repo and follow the steps, or just read along - your choice!

git clone --branch withoutInclude git@github.com:Leinnan/bevy_tiled_blueprints.git

If we use our command to check what will be included in the package:

cd bevy_tiled_blueprints
cargo package --list --allow-dirty

Some of the files that would be included don't exist at this moment, but that doesn't matter for our demonstration. We want to have a broad understanding of what will be included in the package.

It should output like this:

.cargo_vcs_info.json
.github/workflows/rust.yml
.gitignore
CHANGELOG.md
Cargo.toml
LICENSE-APACHE
LICENSE-MIT
README.md
assets/map.tmx
assets/textures/colony-grounds-ready.png
examples/simple.rs
index.html
simple_example.png
simple_example_tiled.png
src/debug.rs
src/lib.rs

To get more information lets do a dry run of publish and check the package size:

cargo publish --dry-run --allow-dirty
du -h target/package/*.crate

Output:

264K    target/package/bevy_tiled_blueprints-0.1.0.crate

The package isn't enormous, but it's larger than it needs to be. There's no reason to include image and TMX files in the package. We can reduce the package size by removing unnecessary files.

First, let's check the exclusion approach. Edit Cargo.toml and add an exclude field to the package section:

exclude = ["*tmx", "*png"]

Let's check what will be included in the package now by calling the same command, output now should be:

.cargo_vcs_info.json
.gitignore
CHANGELOG.md
Cargo.lock
Cargo.toml
Cargo.toml.orig
LICENSE-APACHE
LICENSE-MIT
README.md
examples/simple.rs
index.html
src/debug.rs
src/lib.rs
48K    target/package/bevy_tiled_blueprints-0.1.0.crate

One fifth of the package size!

We can do a bit better by taking the opposite approach. Instead of excluding files, we can include only the necessary ones.

To do so, edit Cargo.toml, make sure there is no exclude field, and add an include field to the package section:

include = ["*rs"]

Let's check what will be included in the package now by calling the same command, output now should be:

.cargo_vcs_info.json
Cargo.lock
Cargo.toml
Cargo.toml.orig
src/debug.rs
src/lib.rs
44K    target/package/bevy_tiled_blueprints-0.1.0.crate

Even less! We've reduced the package size significantly, and now it contains only the necessary files.

This work is licensed under CC BY-NC-SA 4.0.

avatar

Piotr Siuszko

GameDev tool engineer, C#/C++ at day, Rust at night.