patina-ai 0.23.0

Context orchestration for AI development - captures and evolves patterns over time
Documentation
---
id: dependable-rust
layer: core
status: active
created: 2025-08-11
tags: [architecture, rust, black-box, module-pattern]
references: [unix-philosophy]
---

# Dependable Rust

**Purpose:** Keep a tiny, stable external interface and push changeable details behind a private internal implementation module. Easy to review, document, and evolve.

---

## Core Principle

Keep your public interface small and stable. Hide implementation details in `internal.rs` or `internal/` and never expose them in public signatures. This creates black-box modules that can be completely rewritten internally without breaking users.

**Not a line count rule - a design principle.**

## When to Use

Apply this pattern when you need to isolate change or manage complexity:

**Use internal.rs for:**
- ✅ Adapters bridging external systems (hide vendor details)
- ✅ Complex modules with many private helpers
- ✅ Code you expect to rewrite often (isolate churn from interface)

**Don't split when:**
- ✅ Simple commands (sequential steps, no abstraction needed)
- ✅ Naturally small modules (one file is clearer)
- ✅ Procedural code with no hidden complexity

## How to Apply

### 1. Canonical Layout

```
module/
├── mod.rs          # External interface: docs + curated exports
└── internal.rs     # Internal implementation details (or `internal/` folder)
```

### 2. External Interface Rules (`mod.rs`)

- Keep interface small: module docs, type names, minimal constructors, `pub use` statements
- No references to `internal::` in public signatures
- Provide a single `Error` enum (`#[non_exhaustive]` if appropriate)
- Add at least one runnable doctest showing usage

### 3. Internal Implementation Rules (`internal.rs` or `internal/`)

- Default to `pub(crate)`; only the external interface decides what becomes `pub`
- Keep helpers and heavy logic here
- Split into files when it grows: `internal/{mod,parse,exec,validate}.rs`
- Use trait objects or sealed traits internally; export stable traits only when necessary

### 4. Wiring Options

**A) Re-export items defined in `internal` (fast iteration)**

```rust
// mod.rs
mod internal; // private
pub use internal::{Client, Config, Result, run};
```

**B) Define types in `mod.rs`, impls in `internal` (stable names)**

```rust
// mod.rs
mod internal;
pub struct Client { /* private fields */ }
// heavy impls live in internal.rs
```

### 5. Visibility Pattern

```rust
// External interface (mod.rs)
mod internal;                 // not `pub`
pub use internal::{Client};   // curate API

// Internal implementation (internal.rs)
pub(crate) struct Engine;     // crate-internal helper
```

## The "Do X" Test

Before creating a module, ensure you can clearly state what it does in one sentence:

**Good "Do X" (Clear modules):**
- ✅ "Parse TOML into data structure"
- ✅ "Compress files using gzip"
- ✅ "Calculate hash of data"

**Bad "Do X" (Unclear scope):**
- ❌ "Manage workspaces" (too vague - what does "manage" mean?)
- ❌ "Handle project initialization" (multiple responsibilities)
- ❌ "Integrate with build tools" (unbounded scope)

**When "Do X" is unclear:**
1. **Split it**: Break into multiple black boxes
2. **Layer it**: Stable core + replaceable adapters
3. **Accept it**: Some code is glue - optimize for clarity over permanence

## Testing Strategy

- **Doctests** in `mod.rs` show intended usage
- **Unit tests** colocated under `internal/*` for edge cases
- **Integration tests** in `tests/` exercise only the external interface

## Enforcement

Check for pattern violations:

**What to check:**
- ✅ No `pub mod internal` (keeps internal private)
- ✅ No `internal::` in public function signatures (prevents leakage)
- ✅ Internal types not exposed in public API

**When to check:**
- During code review
- When interface feels cluttered
- When adding new public API

## Common Mistakes

**1. Exposing internal types in public API**
```rust
// ❌ Bad: leaks implementation
pub fn get_engine(&self) -> &internal::Engine

// ✅ Good: opaque or re-exported type
pub fn get_engine(&self) -> &Engine  // re-exported from internal
```

**2. Making internal module public**
```rust
// ❌ Bad: users can bypass your API
pub mod internal;

// ✅ Good: keep private
mod internal;
pub use internal::{SelectedTypes};
```

**3. Splitting when unnecessary**
```rust
// ❌ Bad: artificial split for simple procedural code
// mod.rs (30 lines of boilerplate)
// internal.rs (40 lines of sequential steps)

// ✅ Good: keep simple code simple
// mod.rs (70 lines - clear, sequential, no abstraction needed)
```

## Cross-Language Quick Bridge

For teammates familiar with other languages:

- **C:** `module.h` (small) + `module.c` (guts) ≈ `mod.rs` + `internal.rs`
- **TypeScript:** `index.ts` (exports) + `internal.ts` (not exported) ≈ `mod.rs` + `internal.rs`
- **Go:** `api.go` in public package + `internal/``mod.rs` + `internal.rs`

## References

- [Unix Philosophy]./unix-philosophy.md - Decomposition principle (systems → tools)
- [Adapter Pattern]./adapter-pattern.md - When building external bridges
- [Eskil Steenberg Rust]../surface/eskil-steenberg-rust.md - Philosophy behind stable APIs