Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
373 changes: 373 additions & 0 deletions .claude/skills/rust/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,373 @@
---
name: rust
description: Rust coding best practices for idiomatic, efficient, and maintainable code. Use when writing Rust code, reviewing code, or learning Rust patterns.
allowed-tools: Read, Edit, Write, Bash, Grep, Glob, Task
---

# Rust Best Practices

Guidelines for writing idiomatic, efficient, and maintainable Rust code.

## Core Principles

1. **Leverage the type system** - Make invalid states unrepresentable
2. **Prefer compile-time checks** - Catch errors before runtime
3. **Be explicit about ownership** - Don't fight the borrow checker
4. **Write code that passes fmt/clippy first** - Not after fixing

## Code health

1. Prefer smaller files, refactor large files into multi-file modules
2. Refactor large modules into several
3. Prefer re-usable functions. Don't create separate functions if re-use is not planned.

## Error Handling

Use `thiserror`

```rust
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ConfigError {
#[error("Failed to read config: {0}")]
Io(#[from] std::io::Error),

#[error("Failed to parse config: {0}")]
Parse(#[from] toml::de::Error),

#[error("Invalid configuration: {message}")]
Invalid { message: String },
}
```

### Never Use `.unwrap()`

```rust
// BAD
let value = map.get("key").unwrap();

// GOOD
let value = map.get("key").ok_or_else(|| Error::MissingKey("key"))?;

// GOOD (when None is truly impossible)
let value = map.get("key").expect("key always present after init");
```

## Ownership & Borrowing

### Prefer Borrowing Over Cloning

```rust
// BAD - unnecessary clone
fn process(data: String) { ... }
process(my_string.clone());

// GOOD - borrow when possible
fn process(data: &str) { ... }
process(&my_string);
```

### Use `Cow` for Flexible Ownership

```rust
use std::borrow::Cow;

fn process(data: Cow<'_, str>) -> Cow<'_, str> {
if data.contains("bad") {
Cow::Owned(data.replace("bad", "good"))
} else {
data // No allocation if unchanged
}
}
```

### Return Owned Data from Constructors

```rust
// GOOD - clear ownership
impl User {
pub fn new(name: impl Into<String>) -> Self {
Self { name: name.into() }
}
}
```

## API Design

### Builder Pattern for Complex Configuration

```rust
#[derive(Default)]
pub struct ServerBuilder {
host: Option<String>,
port: Option<u16>,
timeout: Option<Duration>,
}

impl ServerBuilder {
pub fn host(mut self, host: impl Into<String>) -> Self {
self.host = Some(host.into());
self
}

pub fn port(mut self, port: u16) -> Self {
self.port = Some(port);
self
}

pub fn build(self) -> Result<Server, ConfigError> {
Ok(Server {
host: self.host.unwrap_or_else(|| "localhost".into()),
port: self.port.ok_or(ConfigError::MissingPort)?,
timeout: self.timeout.unwrap_or(Duration::from_secs(30)),
})
}
}
```

### Newtype Pattern for Type Safety

```rust
// BAD - easy to mix up
fn transfer(from: i64, to: i64, amount: i64) { ... }

// GOOD - compile-time safety
pub struct AccountId(i64);
pub struct Amount(i64);

fn transfer(from: AccountId, to: AccountId, amount: Amount) { ... }
```

### Use `#[must_use]` for Important Returns

```rust
#[must_use]
pub fn validate(&self) -> Result<(), ValidationError> {
// ...
}
```

## Collections & Iterators

### Prefer Iterators Over Loops

```rust
// BAD
let mut results = Vec::new();
for item in items {
if item.is_valid() {
results.push(item.transform());
}
}

// GOOD
let results: Vec<_> = items
.into_iter()
.filter(|item| item.is_valid())
.map(|item| item.transform())
.collect();
```

### Use `collect()` Type Inference

```rust
// Collect into Vec
let vec: Vec<_> = iter.collect();

// Collect into HashMap
let map: HashMap<_, _> = iter.collect();

// Collect Results
let results: Result<Vec<_>, _> = iter.collect();
```

## Async Patterns

### Use `tokio` for Async Runtime

```rust
#[tokio::main]
async fn main() -> Result<()> {
let result = fetch_data().await?;
Ok(())
}
```

### Avoid Blocking in Async Code

```rust
// BAD - blocks the runtime
async fn bad() {
std::thread::sleep(Duration::from_secs(1));
}

// GOOD - async sleep
async fn good() {
tokio::time::sleep(Duration::from_secs(1)).await;
}

// GOOD - spawn blocking for CPU-intensive work
async fn compute() -> i32 {
tokio::task::spawn_blocking(|| expensive_computation()).await.unwrap()
}
```

## Testing

### Unit Tests in Same File

```rust
#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_basic() {
assert_eq!(add(1, 2), 3);
}

#[test]
fn test_edge_case() {
assert!(validate("").is_err());
}
}
```

### Integration Tests in `tests/`

```rust
// tests/integration_test.rs
use my_crate::public_api;

#[test]
fn test_full_workflow() {
let result = public_api::process("input");
assert!(result.is_ok());
}
```

### Use `assert!` Macros Effectively

```rust
assert!(condition);
assert_eq!(left, right);
assert_ne!(left, right);
assert!(result.is_ok());
assert!(result.is_err());
assert_matches!(value, Pattern::Variant { .. });
```

## Performance

### Avoid Premature Allocation

```rust
// BAD - allocates even if not needed
fn maybe_string() -> String {
String::from("default")
}

// GOOD - return static str when possible
fn maybe_string() -> &'static str {
"default"
}
```

### Use `Vec::with_capacity` for Known Sizes

```rust
// BAD - multiple reallocations
let mut vec = Vec::new();
for i in 0..1000 {
vec.push(i);
}

// GOOD - single allocation
let mut vec = Vec::with_capacity(1000);
for i in 0..1000 {
vec.push(i);
}
```

### Profile Before Optimizing

```bash
cargo build --release
cargo flamegraph # requires cargo-flamegraph
```

## Module Organization

### Keep Modules Focused

```rust
// src/lib.rs
pub mod config;
pub mod client;
pub mod error;

// Re-export public API
pub use config::Config;
pub use client::Client;
pub use error::Error;
```

### Use `pub(crate)` for Internal APIs

```rust
// Public to crate, not external users
pub(crate) fn internal_helper() { ... }
```

## Documentation

### Document Public APIs

```rust
/// Creates a new client with the given configuration.
///
/// # Arguments
///
/// * `config` - The client configuration
///
/// # Errors
///
/// Returns an error if the configuration is invalid.
///
/// # Examples
///
/// ```
/// let client = Client::new(Config::default())?;
/// ```
pub fn new(config: Config) -> Result<Self> {
// ...
}
```

## Anti-Patterns to Avoid

| Anti-Pattern | Better Approach |
|--------------|-----------------|
| `.unwrap()` everywhere | Use `?` operator |
| `clone()` to satisfy borrow checker | Restructure ownership |
| `String` parameters | Use `&str` or `impl Into<String>` |
| Boolean parameters | Use enums |
| Long function bodies | Extract to smaller functions |
| Deep nesting | Use early returns |
| Magic numbers | Use named constants |

## Quick Reference

```bash
# Quality gates
cargo fmt -- --check && cargo clippy -- -D warnings && cargo test

# Common cargo commands
cargo check # Fast syntax/type check
cargo build # Debug build
cargo build --release # Release build
cargo nextest run # Run tests
cargo doc --open # Generate and view docs
cargo clippy --fix # Auto-fix lint issues
```
20 changes: 1 addition & 19 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,2 @@
# Repository Guidelines

## Project Structure & Module Organization
PgDog is a Rust workspace centred in `pgdog/` with the proxy under `pgdog/src` and end-to-end specs in `pgdog/tests`. Shared macros live in `pgdog-macros`, runtime extensions in `pgdog-plugin` plus `pgdog-plugin-build`, and loadable plugins under `plugins/`. Integration harnesses, Docker assets, and helper scripts sit in `integration/`, `docker/`, and `dev/`. Use `example.pgdog.toml` and `example.users.toml` as templates when adding sharded configs.

## Build, Test, and Development Commands
Run `cargo check` for a quick type pass, and `cargo build` to compile local binaries; switch to `cargo build --release` for benchmarking or packaging. Start the development stack with `bash integration/dev-server.sh`, which provisions dependencies and runs `cargo watch` for live reloads. CI parity tests run via `cargo nextest run --test-threads=1 --no-fail-fast`. **Never invoke** `cargo test` directly—always use `cargo nextest run --test-threads=1 ...` for unit or integration suites so concurrency stays deterministic.

## Coding Style & Naming Conventions
Follow Rust conventions: modules and functions in `snake_case`, types in `UpperCamelCase`, constants in `SCREAMING_SNAKE_CASE`. Keep modules under ~200 lines unless justified. Format with `cargo fmt` and lint using `cargo clippy --all-targets --all-features` before posting a PR.
Prefer keeping `#[cfg(test)]` blocks at the end of a file; only place `#[cfg(test)]` imports directly beneath normal imports when that keeps the module tidy.

## Testing Guidelines
Adhere to TDD—write the failing test first, implement minimally, then refactor. Co-locate unit tests with their crates, reserving heavier scenarios for `integration/` against the prepared-transaction Postgres stack. Invoke `cargo nextest run --test-threads=1 <test>` for focused iterations; gate Kerberos coverage behind `--features gssapi`. Do **not** run `cargo test`; Nextest with a single-thread budget is the required harness.

## Commit & Pull Request Guidelines
Use concise, imperative commit subjects (e.g., "fix gssapi credential lookup") and describe user impact plus risk areas in PRs. Link relevant issues, attach logs or test output, and include config diffs or screenshots for operational changes. Keep PR scope tight and ensure linting, formatting, and tests pass locally before requesting review.

## Security & Configuration Tips
Capture diagnostics before code changes, prefer configuration-driven fixes, and document new setup steps in `integration/README`. Never commit secrets; rely on the provided templates and environment variables when adjusting credentials or connection strings.
Read `CLAUDE.md` and `.claude/skills/rust/SKILL.md`.
Loading
Loading