S28: Too many or unknown dependencies
What it looks like
Section titled “What it looks like”A small command-line tool that reads a list of items and prints a receipt:
[package]name = "receipt-tool"version = "0.1.0"edition = "2024"
[dependencies]chrono = "*" # any version, ever: also future breaking onesitertools = "0.15" # for one call to `join`lazy_static = "1" # `std::sync::LazyLock` does thisleft-pad = "1" # `format!("{:>8}", x)` does thisregex = "1" # to split a line on commasserde = { version = "1", features = ["derive"] } # nothing is serializedtokio = { version = "1", features = ["full"] } # the program has no async codeAll seven crates are real, and the manifest resolves on crates.io. CI checks that. But the program needs none of them.
Why AI agents produce it
Section titled “Why AI agents produce it”- A crate for each task. Training data has many answers of the form “use the X crate”. The agent adds X without checking if
stdalready does it. - Copying a template.
tokiowithfeatures = ["full"]andserdewithderiveare in a large part of Rust examples, so they appear in projects that do not use them. - Easy versions.
"*"always resolves, so the agent never gets a version error. - No cost is visible to the agent. A new line in
Cargo.tomllooks small. The agent does not see the build time or the dependency tree.
Why it is a problem
Section titled “Why it is a problem”- Each crate is code that you trust. A crate’s
build.rsand procedural macros run on your machine and in CI at build time. Every dependency, and every dependency of a dependency, is part of your supply chain. - Build time and size.
tokiowithfullandregexadd many crates and seconds to every clean build.cargo treeshows the whole tree. "*"accepts any future version, also one with breaking changes.Cargo.lockprotects a binary until the nextcargo update. crates.io rejects a crate with a*dependency when you publish it.- Unknown crates. A crate with few downloads, one author, and no repository link may be unmaintained, or worse. See S26: Hallucinated crate, API, or feature flag for crate names that agents invent.
- Outdated crates.
lazy_staticis replaced bystd. See S27: Outdated idioms (try!,extern crate,lazy_static, old edition).
The better version
Section titled “The better version”The standard library does all of it:
use std::collections::HashMap;use std::fmt::Write;use std::sync::LazyLock;
/// Was `lazy_static`: now `std::sync::LazyLock`.static PRICES: LazyLock<HashMap<&str, u64>> = LazyLock::new(|| HashMap::from([("book", 2_000), ("pen", 150)]));
/// Was `regex` for splitting on commas: now `str::split`.pub fn parse_items(line: &str) -> Vec<&str> { line.split(',') .map(str::trim) .filter(|s| !s.is_empty()) .collect()}
/// Was `itertools::join` and a padding crate: now `format!` with a width.pub fn receipt(items: &[&str]) -> String { let mut out = String::new(); for item in items { let cents = PRICES.get(item).copied().unwrap_or(0); let _ = writeln!(out, "{item:<10}{cents:>8}"); // writing to a `String` cannot fail } out}| Crate in the “worse” manifest | Replacement |
|---|---|
lazy_static |
std::sync::LazyLock |
regex to split on commas |
str::split |
left-pad |
format!("{item:<10}{cents:>8}") |
itertools for join |
slice::join, or writeln! in a loop |
chrono, serde, tokio |
not used: remove them |
The “better” Cargo.toml has an empty [dependencies] section.
When it is acceptable
Section titled “When it is acceptable”A dependency is right when the crate does real work that is hard to get right: serde for serialization, tokio for an async server, regex for real patterns, chrono or jiff for time zones. Then check:
- The crate: downloads, the repository link, recent releases, and the maintainers on crates.io.
- The version: a caret version such as
"1.0.229"(Cargo reads it as^1.0.229), never"*". - The features: only the ones the code uses, such as
tokiowithrtandmacrosinstead offull.
How to detect it
Section titled “How to detect it”- Read every change to
Cargo.tomlandCargo.lock. A new line inCargo.tomlcan add dozens of crates toCargo.lock. cargo treeshows the dependency tree.cargo tree -dshows crates that are in the tree in more than one version.cargo machetefinds dependencies that the code does not use.cargo auditchecksCargo.lockagainst the RustSec advisory database.cargo denycan ban crates, licenses, and wildcard versions.- Clippy
wildcard_dependencies(in thecargogroup, off by default) reports"*"versions.
What to tell your agent
Section titled “What to tell your agent”Do not add a dependency for something the standard library does. For eachdependency you add, say what it is for, and why std is not enough. Use caretversions such as "1.2.3", never "*". Enable only the features the code uses.Remove dependencies that the code does not use. Show the output ofcargo tree for the new dependencies.Related: S26: Hallucinated crate, API, or feature flag, S27: Outdated idioms (try!, extern crate, lazy_static, old edition), Modules, crates, and Cargo, and Tooling.