Code style
AOS uses the established style of each language. rustfmt formats Rust and
Alejandra formats Nix. Generated, vendored, and upstream source keeps its
existing style.
#Rust
Public APIs use standard traits and names. Conversions implement From or
TryFrom when those contracts fit. An as_ method returns a cheap borrowed
view, to_ creates a new value, and into_ consumes self. Collections expose
the usual iter, iter_mut, and into_iter methods. Display provides the
user-facing representation; Debug provides the diagnostic one.
A newtype fits when same-representation values could be swapped accidentally or
construction must validate them. An enum makes a mode readable where a boolean
would produce run(task, true). A parameter struct or builder replaces a long
list of optional arguments. Functions borrow values used only during the call
and take ownership when they retain or consume them. Invariant-bearing types
keep fields private and validate them in constructors.
? and early returns keep edge cases from enclosing the operation. match
fits exhaustive alternatives; if let fits a single relevant pattern.
Iterator chains fit uniform transformations. A loop reads more clearly once
the body carries mutable state, branches several ways, or performs effects
between steps.
Public library APIs return typed errors. A wrapping error retains its source
and adds the failed operation or subject, such as a manifest name or path;
converting the source to a formatted string discards that chain. Production
code contains no .unwrap() or .expect(). Tests and examples may panic when
the panic is the assertion.
AOS uses unsafe only for a specific requirement that safe Rust cannot
reasonably meet. Each unsafe block has a preceding // SAFETY: explanation of
the invariants that make it sound.
Blank lines separate multi-statement setup, validation, external effects, and result construction. Extracting a helper can remove a nested branch, consolidate repeated logic, or give a multi-step operation a name at its call site. A forwarding wrapper has a role when it enforces policy, stabilizes an interface, or creates a test seam.
AOS has no hard function-length limit. Around 100 logical lines prompts a look
for mixed abstraction levels, growing state, or deep nesting; an ordered
protocol or state machine may still read best as one function. A hand-written
file approaching 1,000 lines prompts the same review at module scale. Beyond
roughly 1,500 lines, the interface and module documentation explain its
cohesion. Co-located #[cfg(test)] code counts separately.
Comments record safety arguments, ordering, atomicity, wire compatibility, security policy, and the reason behind a surprising choice. They sit next to the code that relies on the constraint. AOS has no comment-density target.
Every crate root and module starts with a //! overview. Public items have
/// documentation; Result, panic, and unsafe contracts use # Errors,
# Panics, and # Safety. Fenced blocks carry a language tag. Comments on
Clap fields stay concise because they also become command-line help.
Test names state the condition and expected result. Table-driven failures name the case, and golden data includes its regeneration and review procedure.
#Nix
Within modules/, AOS follows the Dendritic pattern: each auto-discovered file
owns one feature's option declarations, configuration, and checks. Its path
names the feature, such as services/registry-hub.nix, rather than a layer such
as options.nix or config.nix. Files under systems/ compose features and
describe variant-specific choices. modules/default.nix remains the discovery
point, and _-prefixed paths hold deliberately imported implementation details.
Package expressions under pkgs/ are the callPackage-style exception. A
package file builds one package from explicit dependencies; modules consume
packages and attach system policy.
Package and helper functions name their dependencies in the argument set.
Module functions retain ... for arguments supplied by the module system.
let introduces derived values; rec remains for genuine sibling references.
Qualified names such as lib.mkIf preserve provenance that a broad with
scope hides. inherit (source) name identifies the source explicitly.
Complex interpolations get a named binding before an embedded shell string.
Generated JSON uses builtins.toJSON rather than hand-written escaping.
Attribute-set updates with // are shallow, so nested configuration uses
module merging or an explicit recursive update when intended. URLs are quoted
strings, and repository expressions do not depend on impure <...> lookup
paths.
Package files read in a stable order: arguments, version and source, dependencies, phases, outputs, and metadata. The hermetic package rules determine every tool and library. Reusable builders and substantial checks receive names and files of their own.
Around 1,000 hand-written lines prompts a review for a separable feature, builder, or test family. Beyond roughly 1,500 lines, the change explains why one file remains easier to navigate. Generated code and declarative inventories follow their natural size.
Short package phases read naturally inline. Repeated sequences, independently testable stages, and dense multi-language escaping favor a named script or helper; around 150 lines is a review point rather than a limit. Embedded shell uses the derivation's shell syntax and AOS-built tools. Paragraph breaks separate setup, execution, validation, and publication. Comments identify intentional non-zero exits, cleanup ownership, and unusual sandbox assumptions.
#References
The language conventions track the Rust Style Guide, Rust API Guidelines, nix.dev best practices, and the Dendritic pattern.