Keep Public Dependencies Intentional

keep-public-dependencies-intentional

Summary

Public dependencies shape downstream compatibility, API expectations, and maintenance cost. Add or raise them only when the public behavior needs that requirement, using lockfile updates for ordinary compatible refreshes.

Problem

A dependency becomes part of a crate's public contract when its types appear in public APIs. That can force downstream users to care about another crate's versions, feature flags, traits, and semver behavior even when the dependency was only an implementation detail.

Preferred Move

Keep dependency types out of public APIs unless the dependency is part of the abstraction. Use local types, standard library types, or conversion boundaries when the dependency should stay private.

Tradeoff

Exposing a dependency type can be correct when interoperability is the point of the API. If the public contract is intentionally shaped around another crate, document that dependency as part of the abstraction and preserve compatibility carefully.

Agent Instruction

Before exposing a dependency type publicly, decide whether downstream users should depend on that
crate as part of the API. If not, keep the dependency behind a local type or conversion boundary.

Examples

Bad: a library exposes anyhow in its public API, so downstream users lose a stable crate-owned error contract.

pub fn parse(input: &str) -> anyhow::Result<Pattern> {
    todo!()
}

Good: the public API exposes a crate-owned error type. The library can still use anyhow internally if that does not leak across the public boundary.

pub fn parse(input: &str) -> Result<Pattern, ParseError> {
    todo!()
}

References

Source Use Note
M-SIMPLE-ABSTRACTIONS adapts Public APIs should avoid exposing nested generic machinery.