std:: Conventions
This defines the intended
conventions for the Silk standard library. New and refactored std:: APIs must
follow this document; older surfaces may temporarily diverge but
must be migrated as they are touched.
This document exists to keep std:: APIs consistent across modules.
Public vs Internal API#
- Only
exportdeclarations are considered part of the stable public surface. - Non-exported declarations are internal implementation details and may change freely.
- Internal helper packages may exist under names such as
std::internal::...orstd::sys::..., but these are not stable surfaces.
Naming#
- Packages use
std::areanaming (std::strings,std::fs,std::net). - Types use
PascalCase(String,Vector(T),Path,File). - Functions and methods use
snake_case(read_all,push,starts_with). - Constants use
SCREAMING_SNAKE_CASE. - Public parameter names must describe the role of the value. Avoid
underscore-prefixed public parameters such as
_fdor_value; if a backend stub ignores a value today, keep the public spelling meaningful and explain the unsupported behavior in the function documentation. - Internal names should favor domain words over placeholders. Short loop
counters are acceptable in tight numeric loops, but cross-block temporaries,
buffers, handles, and results should use names such as
scratch_buffer,read_result,bytes_read,write_cursor, orappend_errorinstead oftmp,r,n,p, or numbered variants. - Names that encode accident rather than meaning, such as
_2,foo,bar, ortmp2, are not acceptable in stdlib code unless they appear inside a documented example where those exact names are part of the teaching context.
Documentation#
All user-facing std:: APIs must be documented in source using doc comments
(/** ... */ or /// ...) per doc comments.
Documentation coverage rules:
- Every exported declaration must have a non-empty doc comment:
- exported functions (
export fn ...), - exported bindings (
export let .../export const ...), - exported
extdeclarations (export ext ...), - exported type aliases (
export type ...), - exported types (
export struct/export enum/export error/export interface), - exported Formal Silk theories intended for reuse (for example under
std::formal). - Every public method on a type must have a non-empty doc comment:
- instance and static methods declared
public fn ...insideimpl.
Source comments are expected to be useful to a reader:
- Do not stop at placeholder comments that only repeat the declaration kind and
name, such as
Function read,Method drop,Struct File, orType alias Result. - Public API comments should state what the API does, what it owns or borrows, what happens on failure, and any platform/runtime limitations that affect callers.
- Runtime shim comments should explain whether the function is implemented for the target or intentionally returns an unsupported/error sentinel.
- Low-level helpers that use raw pointers, byte lengths, file descriptors, or manually packed data should carry enough comment context for a maintainer to audit the safety invariant without reverse-engineering the surrounding module.
This is enforced by the test suite so the stdlib can be fully documented via
silk doc and surfaced consistently in editor tooling.
The canonical narrative/spec for each module lives under docs/std/. The
source-level doc comments are the machine-consumable layer used by silk doc,
silk man, and editor tooling (hover and completion documentation).
Allocation and Ownership#
std:: should be explicit about allocation:
- Prefer allocation-free views (
Slice(T),Str) for APIs that can operate on borrowed data. - Allocating APIs should accept an explicit allocator (or region) parameter, or clearly document which allocator is used.
- Avoid hidden global allocation in core functionality. Convenience helpers may exist, but must be clearly marked.
String ownership (initial design intent):
- The built-in
stringtype is a UTF-8 byte sequence represented as a{ ptr, len }pair (see literals string and ext). - The stdlib provides an owned string builder/container (
String) whose memory management is explicit and interoperable withstring.
Construction and Defaults#
Public std:: types should be easy to construct correctly, without callers
needing to know internal sentinel values or manually fill out large structs.
- Every public
std::struct should provide an explicit “safe default” constructor: - container/builder types should provide
empty()(preferred over requiringinit(0)), - handle/resource types should provide
invalid()(or an equivalent clearly-named constructor) and ensure methods either: - treat invalid handles as no-ops (for example
close/drop), or - return a recoverable error (for example
InvalidInput) rather than trapping. - If a type’s primary constructor requires configuration (capacity, hash function pointers, etc.), provide a convenience constructor that uses a sensible default:
- for capacity-driven containers:
empty()and a parameterized constructor (init(cap)in the Supported forms; considerwith_capacity(cap)long-term), - for option/config structs: a
default()constructor or clearly-named presets (for exampleread_only()/write_only()). Droptypes must be safe to drop in their default/empty/invalid state and should be idempotent when possible (invalidate the handle/pointer after freeing).- Constructors must not silently “succeed” while discarding failures:
- if
init(cap)allocates, it must return a recoverable error (Result(...)or an optional error return) when allocation fails, empty()exists for infallible construction.
Resource Lifetime and Release Testing#
Public owning/resource APIs must make cleanup obvious and safe.
- Each owning type must document:
- which operation releases ownership (
close,drop,deinit, etc.), - whether repeated cleanup is valid,
- and which states are inert / invalid / empty.
- Cleanup operations should leave the value in an inert state after releasing ownership so accidental double-drop and post-close use are easier to detect and avoid.
- Async or split-ownership APIs must not rely on vague caller discipline:
- either preserve the required lifetime structurally (duplication, ref-counting, owned promise state, etc.),
- or document and test the exact lifetime rule the caller must obey.
- When an owning/resource API is added or changed, land release-path regression coverage for:
- the success path,
- and at least one invalid / error / no-op / early-return cleanup path.
- When the same behavior is visible through the public C ABI, add C99 coverage as well as Zig / Silk coverage.
Errors#
Silk supports both typed errors (typed errors) and
recoverable error values (T? and std::result::Result(T, E)). Public std::
APIs should follow these rules:
- Use
T?when “absence” is the only meaningful error case and no additional error information is required (e.g.pop() -> T?). - Use a result type when callers need to distinguish multiple error causes.
The design target is
std::result::Result(T, E)(see result), withOk(T)/Err(E)cases. - Prefer that the primary API name returns
Result(...)/ optional error, rather than exporting parallel*_resultvariants. - Callers that want to discard error details can:
- compare an optional error return against
None, or - for
Result(T, E): - prefer
match (r)whenTorEmay implementDrop, - use
ResultType.ok_value(r)/unwrap(r)/unwrap_or(r, fallback)/err_value(r)only when the active payload is copy-safe (does not implementDrop). Avoid exporting parallel*_opthelpers that hide error information. - Do not use
boolreturns for fallible operations that can fail for multiple reasons; return an optional error (ErrorType?) orResult(T, E)instead. - Keep OS/runtime-specific error mechanisms (such as POSIX
errno) out of the public surface. Map them into stable, portable error kinds/codes in the top-levelstd::module and confine the platform details tostd::runtime. - Do not expose typed error contracts (
T | ErrorType...) orpanicfor routine runtime failures such as I/O errors or parse failures. PreferResult(...)or an optional error return (ErrorType?whereNoneis success). - Do not call
assert/std::abort()fromstd::APIs. Malformed inputs, invariant violations, and resource exhaustion must be surfaced as recoverable errors.
Concurrency and Thread Safety#
Silk’s hosted task concurrency runs on OS threads. std:: APIs must make it
obvious when values can safely cross task/thread boundaries.
Conventions:
- Prefer immutable value types (no interior mutation) for data that will be shared across tasks.
- For shared mutable state, require explicit synchronization via
std::syncprimitives (Mutex,Condvar, channels). - For handle types that own runtime state, use the
T/TBorrowpattern: Tis an owning, droppable handle (non-copyable in safe code),TBorrowis a non-owning, copyable view intended for passing across tasks while keeping ownership with the creator.- Cancellation must be explicit:
- abortable operations should accept
std::abort_controller::AbortSignalBorrow(often as an optional parameter), - callers should create and own an
AbortControllerand callabort()to request cancellation.
Formal Silk Contracts#
std:: should actively use Formal Silk to document and enforce invariants in
low-level code (buffers, parsing, and pointer/length handling):
- Prefer reusable theories from
std::formal(for exampleslice_well_formedandvector_well_formed) over ad-hoc#require/#assertboilerplate. - Contracts must reflect real runtime invariants (avoid over-strong preconditions that callers cannot prove). When handling untrusted inputs, validate at runtime and return a recoverable error value rather than relying on preconditions.
- See formal verification for the Formal Silk model. Note
that the verifier currently skips
std::...modules; contracts instd::are still valuable as precise documentation and for future verification work.
UTF-8 and Text#
stringis defined as UTF-8 bytes.- APIs that operate on “characters” must be explicit about whether they mean:
- bytes,
- Unicode scalar values (
char), - or grapheme clusters (locale/text-segmentation dependent).
- By default, indexing/slicing is byte-based and does not validate UTF-8 unless an API explicitly says it does.
Platform Baselines#
- Hosted baseline: POSIX behavior for filesystem, networking, and clocks.
- Freestanding baseline: no OS; only core modules are available.
Each hosted API must clearly document which POSIX calls it relies on and which errors are surfaced to callers.
Source repository · Edit this page · View Markdown