Add Rust development guidelines for applications and libraries
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
# Rust Application 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, build instructions, usage
|
||||
- **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
|
||||
- Keep functions short and focused — one responsibility per function
|
||||
|
||||
---
|
||||
|
||||
## 2. Cargo
|
||||
|
||||
```bash
|
||||
cargo add <crate> # Add runtime dependency
|
||||
cargo add --dev <crate> # Add dev dependency
|
||||
cargo remove <crate> # Remove dependency
|
||||
cargo build # Debug build
|
||||
cargo build --release # Release build
|
||||
cargo run # Run debug build
|
||||
cargo run --release # Run release build
|
||||
cargo test # Run all tests
|
||||
cargo fmt # Format code
|
||||
cargo clippy -- -D warnings # Lint (treat warnings as errors)
|
||||
```
|
||||
|
||||
Never edit `Cargo.toml` dependency versions by hand — use `cargo add`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Project Structure
|
||||
|
||||
```
|
||||
project/
|
||||
├── src/
|
||||
│ ├── main.rs # Entry point
|
||||
│ ├── lib.rs # Optional: shared library code
|
||||
│ └── <module>/
|
||||
│ └── mod.rs
|
||||
├── tests/ # Integration tests
|
||||
├── Cargo.toml
|
||||
└── Cargo.lock # Commit for applications, not for libraries
|
||||
```
|
||||
|
||||
A project may have multiple binaries under `src/bin/`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Error Handling
|
||||
|
||||
- Use **thiserror** for defining typed errors in library/domain code
|
||||
- Use **anyhow** for application-level error propagation (`main`, CLI handlers)
|
||||
- Never use `.unwrap()` in production code — use `?` or explicit handling
|
||||
- `.expect("reason")` is acceptable only when the invariant is guaranteed and documented
|
||||
|
||||
---
|
||||
|
||||
## 5. Logging
|
||||
|
||||
Use **tracing** for structured logging.
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
```
|
||||
|
||||
```rust
|
||||
use tracing::{debug, info, warn, error};
|
||||
|
||||
fn main() {
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
|
||||
.init();
|
||||
}
|
||||
```
|
||||
|
||||
Control log level via `RUST_LOG` environment variable:
|
||||
|
||||
```bash
|
||||
RUST_LOG=debug cargo run
|
||||
RUST_LOG=my_app=info,warn cargo run
|
||||
```
|
||||
|
||||
#### Log levels
|
||||
|
||||
| Level | When to use |
|
||||
|-------|-------------|
|
||||
| `debug` | Per-item detail: internal state, cache hits, per-item operations |
|
||||
| `info` | User-visible milestones: process started/finished, file saved |
|
||||
| `warn` | Recoverable issues: fallback used, unexpected but non-fatal state |
|
||||
| `error` | Failures the user must know about — operation cannot continue |
|
||||
|
||||
Never use `println!` for debugging or internal logging.
|
||||
|
||||
---
|
||||
|
||||
## 6. Environment and Secrets
|
||||
|
||||
- Store secrets in `.env` file
|
||||
- Load via **dotenvy** crate and `std::env::var()`
|
||||
- Never commit `.env`
|
||||
|
||||
```rust
|
||||
dotenvy::dotenv().ok();
|
||||
let api_key = std::env::var("API_KEY").expect("API_KEY must be set");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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 live in `tests/`
|
||||
- Use `assert_eq!`, `assert!`, `assert_ne!` — no external test frameworks needed
|
||||
- Test naming: `test_<action>_<context>`
|
||||
|
||||
```rust
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_valid_input() {
|
||||
assert_eq!(parse("42"), Ok(42));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Tooling
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| **rustfmt** | Code formatting |
|
||||
| **clippy** | Linting |
|
||||
| **cargo test** | Testing |
|
||||
| **cargo build --release** | Production build |
|
||||
|
||||
Run before every commit:
|
||||
```bash
|
||||
cargo fmt
|
||||
cargo clippy -- -D warnings
|
||||
cargo test
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Distribution
|
||||
|
||||
For standalone executables:
|
||||
|
||||
```bash
|
||||
cargo build --release
|
||||
# Binary is at: target/release/<project-name>
|
||||
```
|
||||
|
||||
For cross-compilation (e.g. Windows from Linux):
|
||||
```bash
|
||||
cargo install cross
|
||||
cross build --release --target x86_64-pc-windows-gnu
|
||||
```
|
||||
|
||||
`Cargo.lock` **is committed** for applications — it ensures reproducible builds.
|
||||
|
||||
---
|
||||
|
||||
## 10. 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
|
||||
|
||||
---
|
||||
|
||||
## 11. Documentation and Task Management
|
||||
|
||||
- Keep `PROJECT.md` and `CHANGELOG.md` up to date when making changes
|
||||
|
||||
### Task notation
|
||||
|
||||
```rust
|
||||
// TODO: one-liner description of a task to be done
|
||||
// FIXME: one-liner description of a known bug to be fixed
|
||||
```
|
||||
@@ -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
|
||||
```
|
||||
Reference in New Issue
Block a user