Code style guide for Claude sessions working on dimpl.
- Imports
- Constants
- Primary type (matches module name:
foo.rs→struct Foo) - Related types, ordered by first appearance in primary type's fields/variants
implblock for primary type- Helper types and functions
- Standard trait impls (
Display,Debug,From) - Tests (
#[cfg(test)] mod tests)
Example: struct Foo { bar: Bar, baz: Baz } → define Bar before Baz.
Protocol flow dictates ordering throughout:
- Module-level comment documenting the protocol flow
- Imports
- Primary struct (
Client/Server) Stateenum (variants in protocol order)impl PrimaryType(public API:new,handle_*,poll_*)impl State(name(),make_progress(), then handlers in enum order)- Free helper functions (ordered by first use in protocol flow)
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.
Private modules with selective re-exports:
mod certificate;
pub use certificate::Certificate;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 are private. Expose via getter methods.
Doc examples must compile and run as tests. Never use ignore. Use no_run only
when hardware/network required.
Comment unwrap() calls explaining why they're safe:
// unwrap: is ok because we set the random in handle_timeout
let random = self.random.unwrap();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.
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