Add Rust development guidelines for applications and libraries

This commit is contained in:
2026-05-25 18:07:09 +02:00
parent 70a77d4cd1
commit 4f1ba1abbf
3 changed files with 452 additions and 0 deletions
+241
View File
@@ -0,0 +1,241 @@
# Rust Library Development Guidelines
**Document Version:** v1
> **Note on Versioning:**
> - This document version is independent — reused across projects
> - **Project version** source of truth: `Cargo.toml` under `[package]`
> - `CHANGELOG.md` uses project version from `Cargo.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 fmt` before every commit
- Lint with **clippy** — run `cargo clippy -- -D warnings` before every commit
- **snake_case** functions/variables/modules, **PascalCase** types/traits, **SCREAMING_SNAKE_CASE** constants
---
## 2. Cargo
```bash
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 `pub` and re-exported from `lib.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
```rust
// 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.rs` using **thiserror**
- Export all errors from `lib.rs`
- Never use `.unwrap()` — use `?` or explicit handling
- `.expect("reason")` only when the invariant is guaranteed and documented
```rust
// 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:
```toml
[dependencies]
tracing = "0.1"
```
```rust
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 tests` block
- Integration tests in `tests/` test only the public API (as a real consumer would)
- Test naming: `test_<action>_<context>`
```rust
#[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.
```rust
/// 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:
```bash
cargo fmt
cargo clippy -- -D warnings
cargo test
```
---
## 10. Distribution
Build and publish with Cargo:
```bash
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.toml` under `[package]`
- Always ask before bumping the version — never increment automatically
- Update `CHANGELOG.md` before bumping the version
- Breaking changes to the public API require a major version bump
---
## 12. Documentation and Task Management
- Keep `PROJECT.md` and `CHANGELOG.md` up to date when making changes
- `README.md` must contain installation instructions and usage examples for the public API
### Task notation
```rust
// TODO: one-liner description of a task to be done
// FIXME: one-liner description of a known bug to be fixed
```