

# `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 `export` declarations 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::...`
 or `std::sys::...`, but these are not stable surfaces.

## Naming

- Packages use `std::area` naming ([`std::strings`](/silk/docs/std/strings/), [`std::fs`](/silk/docs/std/fs/), [`std::net`](/silk/docs/std/networking/)).
- 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 `_fd` or `_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`, or `append_error` instead of
 `tmp`, `r`, `n`, `p`, or numbered variants.
- Names that encode accident rather than meaning, such as `_2`, `foo`, `bar`,
 or `tmp2`, 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](/silk/docs/language/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 `ext` declarations (`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`](/silk/docs/std/formal/)).
- Every **public method** on a type must have a non-empty doc comment:
 - instance and static methods declared `public fn ...` inside `impl`.

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`, or `Type 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 `string` type is a UTF-8 byte sequence represented as a
 `{ ptr, len }` pair (see [literals string](/silk/docs/language/literals-string/) and
 [ext](/silk/docs/language/ext/)).
- The stdlib provides an owned string builder/container (`String`) whose memory
 management is explicit and interoperable with `string`.

## 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 requiring `init(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; consider `with_capacity(cap)` long-term),
 - for option/config structs: a `default()` constructor or clearly-named presets (for example `read_only()` / `write_only()`).
- `Drop` types 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](/silk/docs/language/typed-errors/)) and
recoverable error values (`T?` and [`std::result::Result(T, E)`](/silk/docs/std/result/)). 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)`](/silk/docs/std/result/) (see [result](/silk/docs/std/result/)),
 with `Ok(T)` / `Err(E)` cases.
- Prefer that the **primary** API name returns `Result(...)` / optional error,
 rather than exporting parallel `*_result` variants.
- Callers that want to discard error details can:
 - compare an optional error return against `None`, or
 - for `Result(T, E)`:
 - prefer `match (r)` when `T` or `E` may implement `Drop`,
 - use `ResultType.ok_value(r)` / `unwrap(r)` / `unwrap_or(r, fallback)` / `err_value(r)` only when the active payload is copy-safe (does not implement `Drop`).
 Avoid exporting parallel `*_opt` helpers that hide error information.
- Do not use `bool` returns for fallible operations that can fail for multiple
 reasons; return an optional error (`ErrorType?`) or `Result(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-level `std::` module and confine the platform details to [`std::runtime`](/silk/docs/std/runtime/).
- Do not expose typed error contracts (`T | ErrorType...`) or `panic` for
 routine runtime failures such as I/O errors or parse failures. Prefer
 `Result(...)` or an optional error return (`ErrorType?` where `None` is
 success).
- Do not call `assert` / `std::abort()` from `std::` 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::sync`](/silk/docs/std/sync/)
 primitives (`Mutex`, `Condvar`, channels).
- For handle types that own runtime state, use the `T` / `TBorrow` pattern:
 - `T` is an owning, droppable handle (non-copyable in safe code),
 - `TBorrow` is 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`](/silk/docs/std/abort_controller/)
 (often as an optional parameter),
 - callers should create and own an `AbortController` and call `abort()` 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`](/silk/docs/std/formal/) (for example `slice_well_formed`
 and `vector_well_formed`) over ad-hoc `#require` / `#assert` boilerplate.
- 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](/silk/docs/language/formal-verification/) for the Formal Silk model. Note
 that the verifier currently skips `std::...` modules; contracts in `std::`
 are still valuable as precise documentation and for future verification work.

## UTF-8 and Text

- `string` is 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.
