Thoughts on Rust's Dependency "Problem"

Rust projects are sometimes criticized for their tendency to encourage dependency bloat. I'd like to outline a couple of reasons why I think this happens, and offer a step towards addressing the issue.

Dependency bloat happens when a project takes on more external dependencies (like supporting libraries/crates) than are deemed necessary. There's no generally accepted rule on where to draw the line between a dependency being warranted or not, so our judgment is more or less limited to our own bias and experience. Let's assume that projects can be bloated and consider why that is a bad thing.

Bloat is Transitive

If you're writing software for your own private consumption, never to be used by anyone else, and the software runs on your own machines (or machines that you rent with your own money), then I have good news for you: nobody else's opinion matters! You can pull in as many dependencies as you think are appropriate, so go ahead and use that is_even crate. What's the worst that can happen? 😅

On the other hand, if your project is in the public sphere and is useful to others, bloat matters. If someone imports your crate, they just inherited all the dependencies your project uses, in addition to whatever other dependencies they have. That may be a drop in the bucket, or it may be the entire bucket! The point is that bloat is exponentially transitive. Every project that depends on a bloated one becomes itself at least a little more bloated, on and on down the line for every project in the dependency chain.

For people who care about system resource usage, or even cognitive complexity, managing dependencies efficiently is important. Before considering a solution (or least part of one), let's consider two reasons why projects may bloat.

Rust's Small Standard Library

Rust is somewhat unique among modern programming languages in that is has a very small standard library. When compared to a language like Go, the difference is apparent. Examples of functionality that Go's standard library has but Rust's does not include:

Rust has a good ecosystem of libraries to support all these cases, and some libraries are even popular enough to be considered "standard" (like serde for serialization or tokio for async), but sometimes you just want to use the single, supported thing and instead have to decide between various crates. It's just not as straightforward as importing whatever the language gives you.

A Good Package Manager

cargo is Rust's integrated build tool, package manager, and test runner. It basically "just works" and, in my opinion, is one of Rust's killer features (which is admittedly kind of sad to say in 2025, but that's the state of modern programming).

cargo makes it very easy to add libraries to your project, either by manually editing Cargo.toml or running cargo add some_package from the command line. Unlike Python's pip, cargo resolves dependencies per-project, which avoids collisions (there's also system-wide caching for projects that share the same dependencies). crates.io is the community package registry, which contains quality crates, so there's really not much friction when going to add something to your project. You just search for what you're looking for, cargo add it to your project, and boom, you're off.

Rust packaging requires no collision management, no manually pasting header and source files, no messing with make/cmake, or choosing between maven, gradle, or ant. It's pretty much a no-fuss experience. If a project isn't diligent in tracking dependencies (or just doesn't care), things can grow to the point of being bloated.

So what can we do?

Crate Tagging by Dependency Types

I'd like to propose providing projects a way to signal what kinds of dependencies they use. Suppose we create a layering system like the following:

Under this scheme, a crate could be automatically assessed and tagged with a layer by the registry when being published. cargo's version requirements could be updated to include optional layer specifiers for enforcement (e.g. other_crate = { version = "1.2.3", layer = 2 }). It'd also be nice to indicate some kind of layer restrictions at different scopes of a project as part of the build process, like in the context of team projects where someone wants to say "this crate shouldn't build if we've accidentally imported a layer 4 crate".

The purpose here is to provide tools for projects to reason about the kinds of dependencies they use. One advantage of the "signaling" approach is that, apart from the automatic tagging by the registry, enforcement is fully in your control. If you don't care, you just don't specify any layer requirements.

Conclusion

Let me close by acknowledging that I don't necessarily think dependency bloat is something to be "solved" in the generic sense, as in there being some universal metric to measure and eliminate it. Dependency management, like many aspects of software, is a game of wrangling constraints and trade-offs specific to your project. The approach outlined above attempts to make a project's dependency management intentions more explicit but not objective.

So that's the gist of it. Contact me on GitHub to share thoughts and criticisms. Thanks!