Library vs Application Conventions
Rule
Classify code before applying policy: published or independently consumed library, shared in-repo crate, application or service, CLI, test support, or tests.
Why
External callers update independently, while application code can change with its callers. The classification determines which half of context-sensitive policy applies.
Do
- Treat published crates and independently consumed APIs as long-lived contracts.
- Treat shared in-repo crates as application code until an outside consumer updates independently.
- Treat applications, services, and CLIs as owners of process-wide setup and concrete integrations.
- Route context-specific choices to their owner pages:
Avoid
- Do not impose published-library abstraction on private application internals.
- Do not use application conveniences in reusable library signatures or global setup.
- Do not assume every
pubitem in an internal crate needs external semver treatment. - Do not classify by crate type alone; classify by who consumes and updates the API.
Example
#![allow(unused)]
fn main() {
// Reusable boundary: callers inspect a stable error type.
pub fn parse_manifest(source: &str) -> Result<Manifest, ManifestError> {
parse_manifest_source(source).map_err(ManifestError::from)
}
// Application boundary: the application owns reporting and context.
pub async fn run_deploy(args: DeployArgs) -> anyhow::Result<()> {
execute_deploy(args).await
}
}
Exceptions
- Keep application internals typed when callers branch on failures.
- Treat an internal API as external when another team, plugin, service, or generated client consumes it independently.