rootcause 0.12.1

A flexible, ergonomic, and inspectable error reporting library for Rust
Documentation
---
description: "Rust programming language coding conventions and best practices"
applyTo: "**/*.rs"
---

# Rust Coding Conventions and Best Practices

Follow idiomatic Rust practices and community standards when writing Rust code.

These instructions are based on [The Rust Book](https://doc.rust-lang.org/book/), [Rust API Guidelines](https://rust-lang.github.io/api-guidelines/), [RFC 430 naming conventions](https://github.com/rust-lang/rfcs/blob/master/text/0430-finalizing-naming-conventions.md), and the broader Rust community at [users.rust-lang.org](https://users.rust-lang.org).

## Rootcause-Specific Constraints

This library is designed to be `#![no_std]` compatible:

- **Prefer `core::` over `std::`** for fundamental types and traits
- **Use `alloc::` for heap-allocated types** like `Vec`, `String`, `Box`, etc.
- **Only use `std::` in feature-gated code** - some files behind feature flags are allowed to use `std`
- **Be mindful of allocations** - the library should work in embedded and constrained environments
- **Use `triomphe::Arc` instead of `std::sync::Arc`** for reference counting (this is already established in the codebase)

## General Instructions

- Always prioritize readability, safety, and maintainability.
- Use strong typing and leverage Rust's ownership system for memory safety.
- Break down complex functions into smaller, more manageable functions.
- For algorithm-related code, include explanations of the approach used.
- Write code with good maintainability practices, including comments on why certain design decisions were made.
- Handle errors gracefully using `Result<T, E>` and provide meaningful error messages.
- For external dependencies, mention their usage and purpose in documentation.
- Use consistent naming conventions following [RFC 430]https://github.com/rust-lang/rfcs/blob/master/text/0430-finalizing-naming-conventions.md.
- Write idiomatic, safe, and efficient Rust code that follows the borrow checker's rules.
- Ensure code compiles without warnings.

## Patterns to Follow

- Use modules (`mod`) and public interfaces (`pub`) to encapsulate logic.
- Handle errors properly using `?`, `match`, or `if let`.
- For rootcause: use `report!()` macro and `Report<T>` types for error handling.
- For other code: use `serde` for serialization and `thiserror` or `anyhow` for custom errors.
- Implement traits to abstract services or external dependencies.
- Structure async code using `async/await` and `tokio` or `async-std` (when `std` is available).
- Prefer enums over flags and states for type safety.
- Use builders for complex object creation.
- Split binary and library code (`main.rs` vs `lib.rs`) for testability and reuse.
- Use `rayon` for data parallelism and CPU-bound tasks (when `std` is available).
- Use iterators instead of index-based loops as they're often faster and safer.
- Use `&str` instead of `String` for function parameters when you don't need ownership.
- Prefer borrowing and zero-copy operations to avoid unnecessary allocations.
- Use `core::` and `alloc::` imports instead of `std::` when possible for no_std compatibility.

### Ownership, Borrowing, and Lifetimes

- Prefer borrowing (`&T`) over cloning unless ownership transfer is necessary.
- Use `&mut T` when you need to modify borrowed data.
- Explicitly annotate lifetimes when the compiler cannot infer them.
- Use `alloc::rc::Rc<T>` for single-threaded reference counting and `triomphe::Arc<T>` for thread-safe reference counting (rootcause uses `triomphe` for no_std compatibility).
- Use `core::cell::RefCell<T>` for interior mutability in single-threaded contexts and `Mutex<T>` or `RwLock<T>` for multi-threaded contexts (when `std` is available).

## Patterns to Avoid

- Don't use `unwrap()` or `expect()` unless absolutely necessary—prefer proper error handling.
- Avoid panics in library code—return `Result` instead.
- Don't rely on global mutable state—use dependency injection or thread-safe containers.
- Avoid deeply nested logic—refactor with functions or combinators.
- Don't ignore warnings—treat them as errors during CI.
- Avoid `unsafe` unless required and fully documented.
- Don't overuse `clone()`, use borrowing instead of cloning unless ownership transfer is needed.
- Avoid premature `collect()`, keep iterators lazy until you actually need the collection.
- Avoid unnecessary allocations—prefer borrowing and zero-copy operations.

## Code Style and Formatting

- Follow the Rust Style Guide and use `rustfmt` for automatic formatting.
- Keep lines under 100 characters when possible.
- Place function and struct documentation immediately before the item using `///`.
- Use `cargo clippy` to catch common mistakes and enforce best practices.

## Error Handling (Rootcause-Specific)

This library provides structured error reporting, so follow these rootcause-specific patterns:

- Use `Report<T>` as the primary error type for this library
- Prefer `report!()` macro over direct `Report::new()` calls in most cases
- Use `IntoReport` trait for converting external errors into reports
- Use `ResultExt` trait methods like `.into_report()` for ergonomic error handling
- Leverage the library's attachment system to add context to errors
- See [`rootcause.instructions.md`]rootcause.instructions.md for documentation patterns specific to error reporting

### General Error Handling (Non-Rootcause Code)

- Use `Result<T, E>` for recoverable errors and `panic!` only for unrecoverable errors.
- Prefer `?` operator over `unwrap()` or `expect()` for error propagation.
- Create custom error types using `thiserror` or implement `std::error::Error`.
- Use `Option<T>` for values that may or may not exist.
- Provide meaningful error messages and context.
- Error types should be meaningful and well-behaved (implement standard traits).
- Validate function arguments and return appropriate errors for invalid input.

## API Design Guidelines

### Common Traits Implementation

Eagerly implement common traits where appropriate:

- `Copy`, `Clone`, `Eq`, `PartialEq`, `Ord`, `PartialOrd`, `Hash`, `Debug`, `Display`, `Default`
- Use standard conversion traits: `From`, `AsRef`, `AsMut`
- Collections should implement `FromIterator` and `Extend`
- Note: `Send` and `Sync` are auto-implemented by the compiler when safe; avoid manual implementation unless using `unsafe` code

### Type Safety and Predictability

- Use newtypes to provide static distinctions
- Arguments should convey meaning through types; prefer specific types over generic `bool` parameters
- Use `Option<T>` appropriately for truly optional values
- Functions with a clear receiver should be methods
- Only smart pointers should implement `Deref` and `DerefMut`

### Future Proofing

- Use sealed traits to protect against downstream implementations
- Structs should have private fields
- Functions should validate their arguments
- All public types must implement `Debug`

## Testing and Documentation

- Write comprehensive unit tests using `#[cfg(test)]` modules and `#[test]` annotations.
- Use test modules alongside the code they test (`mod tests { ... }`).
- Write integration tests in `tests/` directory with descriptive filenames.
- Write clear and concise comments for each function, struct, enum, and complex logic.
- Ensure functions have descriptive names and include comprehensive documentation.
- Document all public APIs with rustdoc (`///` comments) following the [API Guidelines]https://rust-lang.github.io/api-guidelines/.
- Use `#[doc(hidden)]` to hide implementation details from public documentation.
- Document error conditions, panic scenarios, and safety considerations.
- Examples should use `?` operator, not `unwrap()` or deprecated `try!` macro.

### Safety Documentation

All `unsafe` functions and `unsafe` blocks MUST follow these documentation patterns to ensure safety requirements are clear and easy to verify:

#### Safety Documentation in Docstrings

**ALL safety documentation MUST use numbered lists, even for single requirements:**

**Single requirement:**

```rust
/// # Safety
///
/// The caller must ensure:
///
/// 1. `ptr` is valid and properly aligned
```

**Multiple requirements:**

```rust
/// # Safety
///
/// The caller must ensure:
///
/// 1. The pointer comes from [`triomphe::Arc::into_raw`]
/// 2. The context type `C` matches the stored type in [`ReportData`]
/// 3. The pointer is not used after calling this function
```

#### Safety Comments in Implementation

Every `unsafe` block MUST have a `// SAFETY:` comment explaining why the safety requirements **of the unsafe operation being performed** are upheld.

**CRITICAL: The `// SAFETY:` comment addresses the requirements of the unsafe function/operation being called, NOT the requirements of the outer function you're implementing.**

**ALL safety comments MUST use numbered lists, even for single requirements:**

**Single requirement:**

```rust
// SAFETY: The safety requirement for `ptr.read()` is upheld:
// 1. The pointer is valid and properly aligned because we just created it from a valid reference above
unsafe { ptr.read() }
```

**Multiple requirements:**

```rust
// SAFETY: The safety requirements for `from_raw` are upheld:
// 1. The pointer comes from `Arc::into_raw` as guaranteed by the caller
// 2. The context type matches because we don't perform any type transmutation
// 3. The pointer is not reused after this call due to the move semantics
unsafe { Report::from_raw(raw) }
```

**Rules for LLMs:**

1. **Identify what unsafe operation is being performed** (e.g., calling `from_raw`, dereferencing a pointer, calling a vtable function)
2. **Look up that operation's safety requirements** (from its `/// # Safety` docs if it's a function, or from Rust docs for intrinsics)
3. **Always use numbered lists**: Whether 1 requirement or multiple, always use numbered list format (1. 2. 3.)
4. **Explain WHY each requirement is satisfied**, don't just repeat what the requirement says
5. **Include helpful context** when it aids understanding (e.g., "We know that `self.drop` points to `drop::<A>` below")
6. **Never skip the comment** - every `unsafe` block needs one

**Common mistakes to avoid:**

- ❌ Don't use bullet points (`-` or ``) - use numbered lists (`1. 2. 3.`) for multiple requirements
- ❌ Don't repeat the requirement verbatim (e.g., "We must ensure X" → just explain why X is true)
- ❌ Don't be overly verbose - be concise while remaining clear
- ❌ Don't address the outer function's requirements - address the called function's requirements

## Project Organization

- Use semantic versioning in `Cargo.toml`.
- Include comprehensive metadata: `description`, `license`, `repository`, `keywords`, `categories`.
- Use feature flags for optional functionality.
- Organize code into modules using `mod.rs` or named files.
- Keep `main.rs` or `lib.rs` minimal - move logic to modules.

## Quality Checklist

Before publishing or reviewing Rust code, ensure:

### Core Requirements

- [ ] **Naming**: Follows RFC 430 naming conventions
- [ ] **Traits**: Implements `Debug`, `Clone`, `PartialEq` where appropriate
- [ ] **Error Handling**: Uses `Result<T, E>` and provides meaningful error types
- [ ] **Documentation**: All public items have rustdoc comments with examples
- [ ] **Testing**: Comprehensive test coverage including edge cases

### Safety and Quality

- [ ] **Safety**: No unnecessary `unsafe` code, proper error handling
- [ ] **Performance**: Efficient use of iterators, minimal allocations
- [ ] **API Design**: Functions are predictable, flexible, and type-safe
- [ ] **Future Proofing**: Private fields in structs, sealed traits where appropriate
- [ ] **Tooling**: Code passes `cargo fmt`, `cargo clippy`, and `cargo test`