Stdlib
Standard Library Direction
Status: provisional reference
This document captures the current stable direction for the standard library.
For the exploratory design notes behind it, see ../research/stdlib-runtime/stdlib-design. For active priorities, see ../ROADMAP.
Use this file for the stable direction. Use ../research/stdlib-runtime/stdlib-design for the broader exploratory comparisons, language borrowings, and future-facing design space. If this file and the research note ever differ, treat this file as the stable project direction and the research note as exploratory background.
Design Rules
The Concrete stdlib should stay:
- explicit about allocation
- explicit about ownership
- explicit about handles/resources
- bytes-first rather than string-first for low-level APIs
- coherent in naming and verb choice across modules
- small and sharp rather than broad
- neutral about the eventual concurrency/runtime model unless a dependency is unavoidable
For low-level internals, the split is now:
- semantic effects remain visible in public signatures (
with(Alloc),with(File), etc.) - pointer-level implementation unsafety is contained by
trusted fn/trusted impl - foreign boundaries (
extern fn) remain underwith(Unsafe)even inside trusted code
See SAFETY for the full safety model and ../research/language/trusted-boundary for the exploratory design notes.
Capability aliases (e.g., cap IO = File + Console;) can reduce signature repetition in stdlib and user code. See FFI.
It should avoid:
- lazy resource-hiding APIs
- a giant iterator/future ecosystem
- hidden allocation
- overly broad collection sprawl before the fundamentals are solid
- builtin/runtime-hook names leaking directly into the public API surface
Public stdlib APIs should also be simpler and clearer than the builtin/runtime machinery they wrap. The stdlib is part of Concrete’s safety story: if the public surface is confusing, ownership-hiding, or effect-blurring, the language becomes harder to audit even if the compiler internals are sound.
What Would Make It Excellent
The stdlib does not need to become huge to become dramatically better. The biggest gains come from making it:
- more coherent
- more explicit
- more ownership-honest
- more audit-friendly
The core principles are:
Bytes first, not String first.
Bytes,Slice,Text, andPathshould remain the center of low-level I/O, parsing, formatting, and networking.One public vocabulary everywhere. The same verbs and API shapes should recur across modules:
open,create,read,write,write_all,closeget,get_uncheckedset,set_uncheckedinsert,remove,contains,len,is_empty
Builtins stay minimal and ugly; stdlib stays clean. Compiler/runtime hooks can be low-level and implementation-shaped. The user-facing stdlib should wrap them in coherent, typed APIs.
Effects and trust stay visible. The best stdlib for Concrete is one where it is easy to answer:
- does this allocate?
- does this block?
- does this require
Unsafe? - does this rely on
trustedinternally? - what capability does it need?
Systems modules should feel like one family.
fs,net,process,env, andtimeshould share the same approach to typed errors, handles, cleanup, and capability visibility.Collections should be few but excellent. It is better to have a small number of deeply-tested, explicit, low-level collections than a broad and inconsistent collection zoo.
Execution Model Alignment
The stdlib is classified into three layers by host dependency, documented in EXECUTION_MODEL:
| Layer | Modules | Host assumption |
|---|---|---|
| Core | option, result, mem, slice, math, fmt, hash, parse, test | None — pure computation |
| Alloc | alloc, vec, string, bytes, text, deque, heap, ordered_map, ordered_set, bitset, map, set, path | malloc/realloc/free + abort |
| Hosted | io, fs, env, process, net, time, rand | Full POSIX libc |
Every Alloc-layer module inherits abort-on-OOM from std.alloc. Hosted-layer modules use Unsafe (for raw libc extern calls inside trusted wrappers) plus domain-specific capabilities where defined: Network for net, Process for process, Random for rand, Console for io print/println. Some hosted modules (fs, env, time) currently use only Unsafe without a domain capability — adding dedicated capabilities (e.g., File, Env, Clock) is future work. This means:
--report capsshows the full authority chain for any stdlib usage--report allocshows which functions allocate and whether cleanup exists- A future
no_allocexecution profile would reject code that uses any Alloc or Hosted module - A future
core_onlyprofile would restrict to Core-layer modules only
Foundation First
The first wave of stdlib foundation work has landed:
vec,string, andiohave had real correctness/completeness workbytes,slice,text,path, andfsnow existenv,process,net, andargsare implemented
The next stdlib work should build on that foundation instead of restarting it. The trust/effect coherence pass is now in place:
- builtins, stdlib, and user code follow one explicit trust/effect model
Alloc,File,Network,Process, etc. stay visible in public signaturestrustedis used only for internal pointer-level implementation techniquesextern fncalls stay underwith(Unsafe)even inside trusted code
Current Foundation Status
Implemented:
- stronger
vec,string, andio— done bytes— doneslice— done- borrowed text views via
text— done path— done- first real
fs— done envandprocess— donenet(TCP) — doneargs(command-line argument access) — done
Typed error hardening is now in place across fs, net, env, and bytes.
The stdlib deepening arc is now underway. fmt, hash, rand, and time are implemented. io has been hardened with typed error returns.
Still the main near-term stdlib work:
- deepen
fs,net, andprocess - keep error and handle conventions uniform
- expand failure-path and integration testing
- carefully chosen collections
- keep pushing deeper systems-module polish and stronger integration coverage
Collection Priorities
The current collection spine is:
VecHashMapHashSet
The next collection work should stay narrow and low-level.
Highest-priority additions after the current core:
Deque / ring buffer Useful for queues, schedulers, buffering, and general systems work.
Priority queue Important for schedulers, search algorithms, and event-driven systems.
Ordered map / ordered set Tree-based containers for deterministic iteration and range-based access.
Bitset / bit array Useful for flags, dataflow, graph/search algorithms, and compiler-style workloads.
Useful later, but not early priorities:
- fixed-capacity vectors/lists
- arenas / slabs / slot maps
- small inline-buffer collections
The rule is: add collections only when they improve low-level work materially, and keep their ownership, allocation, and error behavior as explicit as Vec and HashMap.
Core Module Direction
std.bytes
Owned byte buffer type for:
- file I/O
- network I/O
- parsing
- formatting
Accessors follow an explicit checked/unchecked split:
get/setare bounds-checked (Option<u8>/bool)get_unchecked/set_uncheckedare raw fast paths with no bounds check
std.slice
Borrowed contiguous views:
- immutable slice
- mutable slice
- explicit pointer + length semantics
std.text
Separate borrowed text views from owned String.
std.path
Paths deserve their own module and types:
- borrowed path view
- owned path buffer
- path manipulation without hidden filesystem effects
std.fs
Handle-oriented file APIs:
- owned file handles
- borrowed handle/view types where needed
- no raw fd-like integers in safe-facing APIs
- typed error returns via
FsErrorenum withResult<T, FsError> File::openandFile::createreturnResult<File, FsError>(null-checked fopen)read_filereturnsResult<Bytes, FsError>;write_filereturnsResult<u64, FsError>(reports bytes written)append_file— open in append mode, returnsResult<u64, FsError>file_exists— probe via fopen, returnsboolread_to_string— read file intoString, returnsResult<String, FsError>- methods on an already-validated
Filehandle (write_bytes,read_bytes,seek,tell,close) keep raw returns
Still planned:
- stronger path integration
- later process/environment interplay
std.env
Environment variable access:
- get/set/unset wrapping libc
get()returnsOption<String>—Nonefor absent vars,Somefor present (including empty)setandunsetremain void
std.process
Unix process control:
- exit, getpid
fork()returnsForkResult(Parent/Child/Err) — 3-variant union, not a standard Resultkill()returnsResult<bool, ProcessError>Child::wait()returnsResult<ExitStatus, ProcessError>with typedExitStatus(Exited with code / Signaled with raw status)spawn(cmd, args)returnsResult<Child, ProcessError>— fork+execvp- Signal constants:
sig_int(),sig_kill(),sig_term() - owned Child handle with wait/pid
std.net
TCP networking layer:
- owned TcpListener and TcpStream handles
- explicit buffer-based read/write
- no hidden runtime coupling
- typed error returns via
NetErrorenum withResult<T, NetError> TcpListener::bindreturnsResult<TcpListener, NetError>(checks socket/setsockopt/inet_pton/bind/listen)TcpListener::acceptreturnsResult<TcpStream, NetError>(checks accept)TcpStream::connectreturnsResult<TcpStream, NetError>(checks socket/connect)TcpStream::write_all— loop until all bytes sent, returnsboolTcpStream::read_all— read until EOF intoBytes, returns total bytes readwrite,readkeepi64returns (negative = error);closestays void
Near-term improvement goals:
- deeper integration coverage with real listener/stream round-trips
- cleaner user-facing naming where builtin hooks still leak through
- keep owned-handle and typed-error patterns uniform with
fsandprocess
std.fmt
Pure-Concrete formatting module:
format_int/format_uint— signed/unsigned decimalformat_hex—0xprefixed hexadecimalformat_bin—0bprefixed binaryformat_oct—0oprefixed octalformat_bool—"true"/"false"pad_left/pad_right— fixed-width padding with fill character
No libc dependency beyond alloc/string.
The long-term goal is for fmt and parse to behave like one coherent subsystem: explicit, buffer-oriented, and round-trip-friendly where appropriate.
std.hash
Pure-Concrete FNV-1a hash:
fnv1a_bytes— hash aBytesbufferfnv1a_string— hash aString
Hash/eq helpers for use as fn pointers with HashMap/HashSet:
hash_u64,hash_i32,hash_i64— multiplicative hash with xorshift mixinghash_string— delegates tofnv1a_stringeq_u64,eq_i32,eq_i64,eq_string— pointer-based equality
Deterministic, no libc dependency.
std.map
Open-addressing hash map with linear probing:
HashMap<K, V>— generic over key/value types- Takes
hash_fn: fn(&K) -> u64andeq_fn: fn(&K, &K) -> boolat construction (Zig-style, no traits) - Initial capacity 16, power-of-2, grows at 75% load factor
- Tombstone deletion preserves probe chains
- API:
new,insert(returnsOption<V>),get(returnsOption<&V>),contains,remove(returnsOption<V>),len,is_empty,clear,drop
std.set
Thin wrapper around HashMap<K, u8>:
HashSet<K>— generic over key type- Takes same
hash_fn/eq_fnasHashMap - API:
new,insert(returnsbool),contains,remove(returnsbool),len,is_empty,clear,drop
std.rand
Thin wrapper over libc rand/srand:
seed(s: u32)— deterministic seedingrandom_int() -> i32— raw random integerrandom_range(lo: i32, hi: i32) -> i32— bounded random in[lo, hi)
std.time
Monotonic clock and sleep via libc clock_gettime/nanosleep/time:
Duration—from_secs,from_millis,from_nanosInstant—now()(monotonic clock),elapsed()(duration since capture)sleep(dur: &Duration)— nanosleep wrapperunix_timestamp() -> i64— seconds since epoch
std.io (hardened)
Typed error handling using generic Result:
IoErrorenum (OpenFailed)File::createandFile::openreturnResult<File, IoError>with null-checked fopenprint,println,print_int,eprintunchanged
std.parse
Inverse of fmt — value parsers for converting strings to typed values:
parse_int(s: &String) -> Option<i64>— handles leading-, returnsNoneon invalid/emptyparse_uint(s: &String) -> Option<u64>parse_hex(s: &String) -> Option<u64>— optional0x/0Xprefix, supports a-f/A-Fparse_bin(s: &String) -> Option<u64>— optional0b/0Bprefixparse_oct(s: &String) -> Option<u64>— optional0o/0Oprefixparse_bool(s: &String) -> Option<bool>—"true"/"false"only
Cursor struct for structured parsing of string input:
Cursor::new(s: &String) -> Cursor— create cursor over stringpos,remaining,is_eof— position queriespeek/advance— character access withOption<char>returnskip_whitespace— skip spaces, tabs, newlinesexpect_char(expected: char) -> bool— consume if matched
No libc dependency beyond what std.string provides.
parse should remain small and explicit:
- value parsing
- a tiny
Cursor - no parser combinator framework
- no hidden allocation
- strong round-trip behavior with
fmt
std.test
Test assertion helpers:
assert_eq<T>(expected, result, message) -> bool— equality checkassert_ne<T>(a, b, message) -> bool— inequality checkassert_true(value, message) -> bool/assert_false(value, message) -> boolassert_gt<T>,assert_lt<T>,assert_ge<T>,assert_le<T>— comparison assertionsstr_eq(a: &String, b: &String) -> bool— byte-level string equalityassert_str_eq(a: &String, b: &String, message) -> bool— string equality assertion
All assertions return bool and print the message on failure.
Error and Result Design
Stdlib APIs use a uniform error pattern:
- Small enum error types per module (e.g.
FsError,NetError,IoError,ProcessError) - Generic
Result<T, ModuleError>for all fallible operations — no module-specific result enums - The
?operator works with both named and generic enum types (patched in Check.lean) std.bytesprovidesget/setreturningOption<u8>/boolfor bounds-safe accessstd.stringprovidesgetreturningOption<char>std.vecprovidesgetreturningOption<&T>
This replaces the earlier approach of per-module result enums (FileResult, ReadResult, WriteResult, ListenResult, StreamResult, KillResult, WaitResult). The generic Result<T, E> is now pub and used everywhere.
Allocation Policy
Allocator-sensitive APIs should make allocation visible:
- via allocator/capability-aware APIs
- via
with(Alloc)when allocation occurs - via return types that make ownership obvious
This fits the implemented three-way split between:
- capabilities (
with(Alloc),with(File), etc.) = semantic effects visible to callers —with(Alloc)stays in public signatures because allocation is a real program behavior callers should know about trusted= containment of internal pointer-level implementation techniques (raw ptr deref, arithmetic, casts) behind a safe API — callers do not needUnsafejust because a container uses raw pointers internallywith(Unsafe)= authority to cross foreign boundaries (FFI, transmute) — always explicit, even insidetrustedcode
See ../research/language/trusted-boundary for the full design.
Later Additions
After the foundation is solid, the likely next additions are:
- a small eager
std.iterif it earns its place std.collections.ordered_map(tree-based)- later
std.sync - later
std.ffi
Current Position
This is not trying to imitate Rust’s breadth.
The goal is a stdlib that is:
- low-level enough for systems work
- explicit enough for auditability
- small enough to stay coherent
The current state is no longer just a plan. A first useful low-level foundation is in place, including the systems layer (env, process, net), with typed error surfaces across the modules that touch the OS. The stdlib deepening arc has added fmt, hash, rand, time, and parse, and unified error handling with generic Result<T, ModuleError> across all modules. Systems modules have been deepened with helper functions (fs: append_file, file_exists, read_to_string; net: write_all, read_all; process: spawn, signal constants).
Module-local stdlib #[test] coverage is now exercised through the real compiler path via concrete std/src/lib.con --test, and recent parser/lowering/codegen fixes were driven directly by making that path execute the stdlib corpus cleanly.
The next arc is not “add lots more modules.” It is:
- cleaner public API names and ownership behavior
- stronger builtin-vs-stdlib separation
- deeper systems-module polish
- stronger failure-path and integration testing
- carefully chosen collections in the order above
The best version of the Concrete stdlib will not be the biggest. It will be the most coherent, explicit, and auditable.