Standard Library Layering and Host Module Boundary
Summary
Define the first standard-library boundary for Metel.
std::core is the small, language-adjacent prelude. It keeps the core sum
types, the core aspects, and List<T> — and, with this revision, it also owns
the collection and iteration ergonomics (map, filter, fold, contains,
find, …) as methods on List<T>, plus the string utility surface (trim,
split, to_upper, contains, chars, …) as methods on String, rather than
splitting these into separate std::collections / std::iter / std::str
modules. The only ordinary (non-prelude, non-host) library module in the first
cut is std::math.
This RFC introduces host-backed library modules under std:: for operating-
system interaction: environment variables (std::env), file operations
(std::fs), and subprocess execution + command-line arguments
(std::process). Networking is explicitly deferred to a later RFC.
This is the canonical definition of the stdlib area for the first presentable release. It supersedes the broader module list in the original draft.
Motivation
Metel is at the point where small scripting tasks are desirable, but the runtime surface still needs a clear layering between:
- Language primitives (
Perhaps,Result,Display,From,Iterable,List<T>) - In-language ergonomics over those primitives (collection/iteration helpers,
Perhaps/Resultcombinators) - Convenience runtime functions (
print,println,dbg,assert) - Host integration (environment, files, processes; networking later)
The first three concerns are language-adjacent and host-independent, so they
live in std::core. The fourth touches the operating system and must be
explicitly imported.
The original draft proposed a wider set of ordinary modules
(std::collections, std::iter, std::cmp, std::math). Experience from
sprint 22 — where List<T> became a real nominal struct in the embedded
std::core source with native methods — shows the collection and iteration
ergonomics are most natural as methods on List<T> in std::core, not as
free functions in satellite modules. This revision tightens the scope
accordingly: one numeric module (std::math) plus the three host modules.
Decisions
1. std::core is the language-adjacent prelude
std::core is the implicit, always-available core module, auto-imported into
every module. It is a real embedded module compiled into the binary,
not a virtual injection. It is not a general-purpose kitchen-sink.
The following items belong in std::core:
Perhaps<T>Result<T, E>List<T>(core collection type, with its ergonomic method surface — see Decision 3)- the
Stringutility surface (methods on the primitiveString— see Decision 4) DisplayFrom<S>Iterable<T>printprintlndbgassert(including the two-argument message overload)
These items are allowed to remain implemented through native host support rather than ordinary user-level library code.
User code writes Perhaps, Result, and List unqualified; the
fully-qualified paths remain std::core::Perhaps, std::core::Result, and
std::core::List. This RFC explicitly rejects moving these names to
std::Perhaps / std::Result. std is a namespace root; core is the home
for language-adjacent primitives.
2. Perhaps / Result utilities live on the core sum types
Perhaps<T> and Result<T, E> gain a small method surface so they are usable
in ordinary pipelines, in std::core itself.
Initial API direction:
Perhaps<T>:map,and_then,unwrap_or,unwrap_or_else,is_some,is_noneResult<T, E>:map,and_then,unwrap_or,unwrap_or_else,is_ok,is_err
These belong with the core sum types, not in a separate convenience module.
3. Collection and iteration ergonomics live on List<T> in std::core
The original draft carved out std::collections and std::iter as separate
modules. This revision folds both into std::core: the ergonomic surface is a
set of methods on List<T> (and, where natural, on arrays), not free functions
in satellite modules.
Initial API direction (methods on List<T>):
- Collection ergonomics:
append/concat,contains(where equality is available),find(pred: (T) -> boolean) -> Perhaps<T> - Iteration transforms:
map,filter,fold
Whether each helper is an inherent method on List<T> or eventually rides an
Iterable<T>-based abstraction is intentionally left open. The first goal is
usefulness over a final iterator architecture; a lazy iterator design remains
deferred to a follow-up RFC. There is no std::collections, std::iter,
or std::cmp module in the first cut — comparison helpers (min/max/clamp)
are covered by std::math (Decision 5).
4. String utilities live on String in std::core
String is a primitive, language-adjacent type — String::len already lives in
std::core as a native method — so its utility surface belongs there too, as
methods on String, not in a separate std::str / std::string module. They
are host-backed (native), computed by the runtime string value.
Indexing is by Unicode scalar value, consistent with String::len (which
counts scalars, not bytes); byte-level and grapheme-level APIs are deferred.
Operations are total — out-of-range indices clamp or yield Perhaps::None
rather than panicking.
Initial API direction (methods on String):
- Case & trim:
to_upper,to_lower,trim,trim_start,trim_end,is_empty(the last derivable asself.len() == 0). - Search & test:
contains(needle),starts_with(prefix),ends_with(suffix),index_of(needle) -> Perhaps<i64>. - Split, join, replace:
split(sep) -> String[],replace(from, to),repeat(n), and the associatedString::join(parts: String[], sep: String) -> String. - Chars & slicing:
chars() -> Char[],char_at(i) -> Perhaps<Char>,substring(start, end) -> String(scalar-indexed, clamped).
Regex, formatting/interpolation, Unicode normalization, and encoding conversions are out of scope for the first cut.
5. std::math is the one ordinary library module
Coverage: untestable (see frontmatter).
std::mathhas not been implemented.
std::math is the single non-prelude, non-host module in the first cut. It is
an ordinary library namespace — not auto-imported; programs import it
explicitly.
Initial API direction:
absminmaxclamp
Additional numeric functionality should be added only when it clearly supports real programs. This RFC does not attempt to define a large numerical library.
6. Host-backed APIs live in explicit modules under std::
Operating-system interaction is not part of the implicit prelude. It must be
placed in explicit modules under std::.
The first host-backed modules are:
std::envstd::fsstd::process
These modules are ordinary library namespaces from the language user's perspective, even though their implementation is host-backed (native). They are not auto-imported. Programs must import them explicitly.
7. Initial scope of std::env
std::env is the boundary for host process environment inspection.
Initial API direction:
var(name: String) -> Perhaps<String>vars() -> List<(String, String)>or[(String, String)]
Read-only APIs are in scope for the first release. Mutating the host process
environment, such as set_var or remove_var, is deferred.
8. Initial scope of std::process
Coverage: untestable (see frontmatter).
std::processhas not been implemented.
std::process covers command-line arguments and subprocess execution.
Initial API direction:
args() -> String[]run(command: String, args: String[]) -> Result<ProcessOutput, OsError>
ProcessOutput is a standard-library data type with at least:
status: i64stdout: Stringstderr: String
The initial subprocess model is synchronous. Streaming stdin/stdout pipes, background processes, and shell-specific helpers are deferred.
The first subprocess API must be shell-free at the Metel boundary. The recommended shape remains:
run(command: String, args: String[]) -> Result<ProcessOutput, OsError>
not:
run(command_line: String) -> ...
This keeps quoting, shell expansion, and platform-specific command parsing out of the first stdlib cut. Users can still invoke a shell explicitly if they want one.
9. Initial scope of std::fs
std::fs covers simple file operations.
Initial API direction:
read_to_string(path: String) -> Result<String, OsError>write_string(path: String, contents: String) -> Result<(), OsError>append_string(path: String, contents: String) -> Result<(), OsError>exists(path: String) -> booleanread_dir(path: String) -> Result<String[], OsError>create_dir(path: String) -> Result<(), OsError>create_dir_all(path: String) -> Result<(), OsError>remove_file(path: String) -> Result<(), OsError>remove_dir(path: String) -> Result<(), OsError>remove_dir_all(path: String) -> Result<(), OsError>
The first file API is text-oriented. Byte arrays, streaming I/O, file handles, and buffered readers/writers are deferred until Metel has a clearer low-level data model for those operations.
read_dir(path: String) is the directory-listing primitive for the first
stdlib cut. It returns directory entry names rather than a metadata-rich entry
type. More specialized helpers such as file-only filtering are deferred until
Metel has a clearer metadata model.
10. A dedicated error type is preferred over Result<T, String>
Host-backed APIs should not standardize on raw String errors.
The initial host modules should use a dedicated standard-library error type,
provisionally named OsError, which at minimum implements Display.
This keeps the boundary open for richer error information later without forcing a breaking redesign of every filesystem and process API.
The exact representation of OsError is deferred. It may begin as an opaque
runtime-backed type.
11. Networking is deferred
Coverage: untestable (see frontmatter). Networking is reserved for a future RFC.
Networking is not part of the first standard-library milestone.
The reasons are architectural, not merely scope-management:
- networking forces decisions about bytes and buffers
- it forces timeout and blocking semantics
- it raises TLS and platform abstraction questions
- it pressures the language toward an async or evented story too early
When networking is added, it should be designed in a dedicated RFC, likely as
std::net with a consciously limited first layer such as HTTP request/response
helpers before lower-level socket APIs.
Module Boundary Rule
The standard library follows this rule:
- If removing the feature would change the language's core semantics, it belongs
in
std::core. - If the feature is a host-independent ergonomic over core primitives, it also
belongs in
std::core(as methods on the relevant core type) unless it forms a coherent standalone domain —std::mathis the one such domain in the first cut. - If the feature depends on the host operating system or external world, it does
not belong in
std::core; it goes in an explicitstd::module.
Examples:
Perhaps,Result,Display,From,Iterable,List<T>(with its collection/iteration methods), and theStringutility methods belong instd::core- numeric helpers (
abs/min/max/clamp) belong instd::math - environment variables, files, subprocesses, and networking do not belong in
std::core
Staging
Prerequisites already landed: System F elaboration (sprint 20) and the
real-embedded-std::core pipeline with native declarations (sprint 22).
The first stdlib implementation pass is therefore built on the
post-elaboration architecture, as the original draft required.
Recommended implementation order:
Perhaps/Resultutility methods instd::coreList<T>collection + iteration ergonomics instd::coreStringutility methods instd::coreOsErrorhost error typestd::envstd::fsstd::processstd::math(after theOrdaspect, RFC-0062)- Public spec / reference updates and tutorial refresh
- Design networking in a dedicated follow-up RFC
Non-Goals
- Defining the full concrete API surface of every host module
- Finalizing whether every collection helper is an inherent method or an
Iterable-based abstraction - Designing a lazy iterator architecture in the first stdlib cut
- String regex, formatting/interpolation, Unicode normalization, byte/grapheme indexing, or encoding conversions (the first cut is scalar-indexed utilities)
- Designing async I/O, futures, or an event loop
- Designing sockets, TLS, or general networking APIs
- Finalizing the representation of
OsError - Introducing
std::collections,std::iter, orstd::cmpas modules in the first cut
References
- RFC-0054:
docs/public/rfcs/4-implemented/rfc-0054-list-type.md - Public spec entry point:
docs/public/reference/spec.md