Skip to content

S28: Too many or unknown dependencies

S28Severity: Medium

Crates added for small tasks, or from unknown authors.

Clippy: wildcard_dependencies

A small command-line tool that reads a list of items and prints a receipt:

Cargo.toml
[package]
name = "receipt-tool"
version = "0.1.0"
edition = "2024"
[dependencies]
chrono = "*" # any version, ever: also future breaking ones
itertools = "0.15" # for one call to `join`
lazy_static = "1" # `std::sync::LazyLock` does this
left-pad = "1" # `format!("{:>8}", x)` does this
regex = "1" # to split a line on commas
serde = { version = "1", features = ["derive"] } # nothing is serialized
tokio = { version = "1", features = ["full"] } # the program has no async code

All seven crates are real, and the manifest resolves on crates.io. CI checks that. But the program needs none of them.

  • A crate for each task. Training data has many answers of the form “use the X crate”. The agent adds X without checking if std already does it.
  • Copying a template. tokio with features = ["full"] and serde with derive are 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.toml looks small. The agent does not see the build time or the dependency tree.
  • Each crate is code that you trust. A crate’s build.rs and 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. tokio with full and regex add many crates and seconds to every clean build. cargo tree shows the whole tree.
  • "*" accepts any future version, also one with breaking changes. Cargo.lock protects a binary until the next cargo 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_static is replaced by std. See S27: Outdated idioms (try!, extern crate, lazy_static, old edition).

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.

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 tokio with rt and macros instead of full.
  • Read every change to Cargo.toml and Cargo.lock. A new line in Cargo.toml can add dozens of crates to Cargo.lock.
  • cargo tree shows the dependency tree. cargo tree -d shows crates that are in the tree in more than one version.
  • cargo machete finds dependencies that the code does not use.
  • cargo audit checks Cargo.lock against the RustSec advisory database. cargo deny can ban crates, licenses, and wildcard versions.
  • Clippy wildcard_dependencies (in the cargo group, off by default) reports "*" versions.
Do not add a dependency for something the standard library does. For each
dependency you add, say what it is for, and why std is not enough. Use caret
versions such as "1.2.3", never "*". Enable only the features the code uses.
Remove dependencies that the code does not use. Show the output of
cargo 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.