Skip to content

Latest commit

 

History

History
130 lines (92 loc) · 3.32 KB

File metadata and controls

130 lines (92 loc) · 3.32 KB

CLAUDE.md

Code style guide for Claude sessions working on dimpl.

File Ordering

  1. Imports
  2. Constants
  3. Primary type (matches module name: foo.rsstruct Foo)
  4. Related types, ordered by first appearance in primary type's fields/variants
  5. impl block for primary type
  6. Helper types and functions
  7. Standard trait impls (Display, Debug, From)
  8. Tests (#[cfg(test)] mod tests)

Example: struct Foo { bar: Bar, baz: Baz } → define Bar before Baz.

State Machine Files (client.rs, server.rs)

Protocol flow dictates ordering throughout:

  1. Module-level comment documenting the protocol flow
  2. Imports
  3. Primary struct (Client / Server)
  4. State enum (variants in protocol order)
  5. impl PrimaryType (public API: new, handle_*, poll_*)
  6. impl State (name(), make_progress(), then handlers in enum order)
  7. Free helper functions (ordered by first use in protocol flow)

Imports

Group: std → external → crate. Alphabetical within groups.

use std::collections::VecDeque;
use std::time::Instant;

use arrayvec::ArrayVec;

use crate::buffer::Buf;
use crate::engine::Engine;

Use short paths: std::Vec not std::vec::Vec.

Modules

Private modules with selective re-exports:

mod certificate;
pub use certificate::Certificate;

Visibility

Prefer pub over pub(crate). Only use pub(crate) when explicitly preventing items from becoming part of the public API. Internal modules that are kept private by their parent don't need pub(crate) on their items.

// Good: parent module is private, so pub here won't leak
mod internal {
    pub struct Helper;  // Not in public API because `internal` is private
}

// Bad: unnecessary restriction
mod internal {
    pub(crate) struct Helper;  // Redundant - parent already private
}

Fields and Methods

Fields are private. Expose via getter methods.

Documentation

Doc examples must compile and run as tests. Never use ignore. Use no_run only when hardware/network required.

Unwrap

Comment unwrap() calls explaining why they're safe:

// unwrap: is ok because we set the random in handle_timeout
let random = self.random.unwrap();

Sans-IO API Contract

dimpl is Sans-IO: no sockets, no threads, no async. The caller drives I/O.

Poll-to-Timeout Rule: Every mutation (handle_packet, handle_timeout) must be followed by polling until Output::Timeout:

client.handle_timeout(now);
loop {
    match client.poll_output(&mut buf) {
        Output::Packet(data) => { /* send to peer */ }
        Output::Connected => { /* handshake complete */ }
        Output::Timeout(when) => break,
    }
}

Never stop polling early. Internal state is only consistent after reaching Timeout.

Memory

Summary: Allocation-conscious design - pool reuse, single-copy, in-place mutation, boxing for ABI, bounded stack collections.

Buffer Pooling

  • Reuse allocations instead of malloc/free per packet
  • Clear contents but retain capacity

Single-Copy Parsing

  • One copy from network into working buffer
  • Parse and decrypt in-place on that buffer

ABI Optimization

  • Box large inner data so outer structs fit in registers
  • Avoids memmove on function calls

Bounded Stack Collections

  • Fixed-capacity arrays for small, known-bounded collections
  • Fail explicitly if bounds exceeded