6.2 KiB
Rust Library Development Guidelines
Document Version: v1
Note on Versioning:
- This document version is independent — reused across projects
- Project version source of truth:
Cargo.tomlunder[package]CHANGELOG.mduses project version fromCargo.toml
Related Documents
- README.md — Project overview, public API description, installation and usage examples
- AGENTS.md — Rules for AI assistants
- PROJECT.md — Project goals and current state
- CHANGELOG.md — Version history
1. Code Style
- Rust edition: 2021
- Format with rustfmt — run
cargo fmtbefore every commit - Lint with clippy — run
cargo clippy -- -D warningsbefore every commit - snake_case functions/variables/modules, PascalCase types/traits, SCREAMING_SNAKE_CASE constants
2. Cargo
cargo add <crate> # Add runtime dependency
cargo add --dev <crate> # Add dev dependency
cargo remove <crate> # Remove dependency
cargo test # Run all tests
cargo doc --open # Build and open documentation
cargo fmt # Format code
cargo clippy -- -D warnings # Lint (treat warnings as errors)
cargo build # Build to verify compilation
cargo publish # Publish to crates.io
Never edit Cargo.toml dependency versions by hand — use cargo add.
2. Project Structure
project/
├── src/
│ ├── lib.rs # Public API — re-export everything the caller needs
│ ├── error.rs # All public error types
│ └── <module>/
│ └── mod.rs
├── tests/ # Integration tests (test the public API only)
├── examples/ # Usage examples
├── Cargo.toml
└── Cargo.lock # Do NOT commit — add to .gitignore
No main.rs — libraries have no entry point.
3. Public API
- Everything intended for external use must be
puband re-exported fromlib.rs - Use
pub(crate)for internal items that cross module boundaries - Internal modules should be private (
mod foo;) unless they are part of the public API - Public API must be stable across patch versions; breaking changes require a major version bump
- Use
#[non_exhaustive]on public enums and structs to allow adding fields without breaking changes
// lib.rs — public surface
pub use error::MyError;
pub use client::Client;
pub use types::{Config, Response};
4. Error Handling
- Define all public error types in
src/error.rsusing thiserror - Export all errors from
lib.rs - Never use
.unwrap()— use?or explicit handling .expect("reason")only when the invariant is guaranteed and documented
// error.rs
use thiserror::Error;
#[derive(Debug, Error)]
pub enum MyError {
#[error("invalid input: {0}")]
InvalidInput(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
}
5. Logging
This is a library. Libraries must never configure logging sinks — that is the responsibility of the consuming application.
Use tracing for instrumentation:
[dependencies]
tracing = "0.1"
use tracing::{debug, info, warn, error};
pub fn do_something() {
debug!("internal detail");
info!("milestone reached");
}
Never call tracing_subscriber::fmt().init() or any sink setup inside library code. The consuming application configures the subscriber.
Log levels
| Level | When to use |
|---|---|
debug |
Per-item detail: internal state, cache hits |
info |
Significant milestones visible to the consuming application |
warn |
Recoverable issues: fallback used, unexpected but non-fatal |
error |
Failures the caller must know about |
6. Environment and Secrets
Libraries do not read environment variables or .env files. Configuration is passed by the caller via arguments or constructor parameters.
7. Testing
- Use Rust's built-in test framework —
#[test]and#[cfg(test)] - Unit tests live in the same file as the code, in a
mod testsblock - Integration tests in
tests/test only the public API (as a real consumer would) - Test naming:
test_<action>_<context>
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_valid_input() {
assert_eq!(parse("42"), Ok(42));
}
}
Cargo.lock is not committed for libraries — add it to .gitignore. Consumers pin their own dependency graph.
8. Documentation
All public items must have doc comments. Use cargo doc --open to verify locally.
/// Parses a value from a string.
///
/// # Errors
///
/// Returns [`MyError::InvalidInput`] if the string is not a valid value.
///
/// # Examples
///
/// ```
/// use my_crate::parse;
/// assert_eq!(parse("42"), Ok(42));
/// ```
pub fn parse(s: &str) -> Result<u32, MyError> { ... }
README.md must contain installation instructions and usage examples for the public API.
9. Tooling
| Tool | Purpose |
|---|---|
| rustfmt | Code formatting |
| clippy | Linting |
| cargo test | Testing |
| cargo doc | Documentation |
Run before every commit:
cargo fmt
cargo clippy -- -D warnings
cargo test
10. Distribution
Build and publish with Cargo:
cargo package # Verify what will be published
cargo publish # Publish to crates.io (requires login)
Cargo.lock is not committed — add to .gitignore.
11. Versioning
- Follow semantic versioning:
MAJOR.MINOR.PATCH - Version is defined in
Cargo.tomlunder[package] - Always ask before bumping the version — never increment automatically
- Update
CHANGELOG.mdbefore bumping the version - Breaking changes to the public API require a major version bump
12. Documentation and Task Management
- Keep
PROJECT.mdandCHANGELOG.mdup to date when making changes README.mdmust contain installation instructions and usage examples for the public API
Task notation
// TODO: one-liner description of a task to be done
// FIXME: one-liner description of a known bug to be fixed