Choosing the Right Rust Crate

Choose a crate by API fit, maintenance, dependency surface, feature model, and trust; popularity is a signal, not a proof of quality or safety.

What it is

Crate choice is architecture. Every dependency adds code you ship, code you build, maintainers you trust, licenses you inherit, and feature flags that Cargo will unify. The question is not “can this crate do the job?” The question is “is this the smallest, maintained, compatible, auditable crate that does the job well?” This note is a decision checklist for adding dependencies to an edition 2024 project. It belongs with Cargo.toml Manifest, Cargo.lock, Feature Flags, and Minimizing Dependencies. It also belongs with supply-chain notes because build.rs scripts and proc macros execute during builds.

How it works

Start from the standard library. If std, alloc, or a tiny local helper covers the need clearly, prefer that. Reach for a crate when it gives tested domain behavior, protocol correctness, format compatibility, platform coverage, or a major ergonomics win. Then evaluate the crate as both API and supply chain. Look at docs.rs for public API, required features, examples, platform notes, and dependencies. Verify the latest version on docs.rs or crates.io before pinning because crate versions move independently of this vault. Review whether the crate exposes stable abstractions or forces its own types into your public API. For libraries, hiding a dependency behind your public API boundary often makes future replacement easier. For binaries, practical velocity can matter more, but the dependency is still code you run.

Cargo features are additive and unified across the graph. A dependency that looks small with default features disabled may become large if another crate enables defaults. Use cargo tree -e features when the feature graph matters. Prefer crates with clear feature documentation and small default sets. Avoid crates that use mutually exclusive features for behavior switches.

Trust review is separate from API review. Check maintainers, release cadence, issue responsiveness, repository links, license, reverse dependencies, advisory status, and whether the crate has build.rs or proc macros. Run cargo-audit and cargo-deny in CI. For higher-assurance projects, use cargo-vet so new crate additions require positive review.

Example

fn pick_http_client(needs_async: bool, needs_json: bool) -> &'static str {
    match (needs_async, needs_json) {
        (true, true) => "reqwest with rustls-tls and json features",
        (true, false) => "reqwest with minimal transport features",
        (false, true) => "ureq or reqwest::blocking after evaluating features",
        (false, false) => "std::net, ureq, or a protocol-specific small crate",
    }
}
 
fn main() {
    let choice = pick_http_client(true, true);
    assert!(choice.contains("reqwest"));
    println!("{choice}");
}

Best practice

  • ✅ Prefer std first, then small focused crates, then broad frameworks.
  • ✅ Read docs.rs before adding the dependency; verify the latest version at the time you edit Cargo.toml.
  • ✅ Check feature flags and start from default-features = false when the crate supports it cleanly.
  • ✅ For workspace projects, centralize versions in [workspace.dependencies]; see Cargo Workspaces.
  • ✅ Run cargo tree, cargo tree -e features, and cargo-audit and cargo-deny before large dependency additions.
  • ✅ Prefer maintained crates with clear ownership, examples, docs, and compatible licenses.
  • ✅ Treat proc macros and build.rs as higher trust requirements, not harmless implementation details.
  • ✅ Commit Cargo.lock for applications so reviewed versions are reproducible.

Pitfalls

  • ⚠️ Picking only by download count; malicious or obsolete crates can have impressive numbers.
  • ⚠️ Copying dependency lines from a blog post without verifying the crate name on crates.io.
  • ⚠️ Adding a framework for a ten-line problem; see Minimizing Dependencies.
  • ⚠️ Letting default features pull in TLS stacks, async runtimes, or proc macros you do not need.
  • ⚠️ Exposing dependency-specific types in a library API before you are sure that crate is a permanent public contract.
  • ⚠️ Ignoring yanked versions, RustSec advisories, duplicate versions, or unknown git dependencies.
  • ⚠️ Assuming Rust memory safety also means dependency trust; see Build-Time Code Execution.

See also

Ecosystem & Crate Playbooks · Minimizing Dependencies · Dependencies and Version Requirements · Feature Flags · Cargo.lock · Cargo Workspaces · Build-Time Code Execution · cargo-audit and cargo-deny · Tokio Runtime Playbook · Reqwest HTTP Client Playbook · Serde Data Format Playbook · Axum Web Service Playbook

Sources