ADR-0078: Stdlib MVP
Status
Implemented.
Summary
Establish the first non-trivial layer of the Gruel standard library by making four changes that, together, turn std/ from a four-function math stub into a real home for stdlib code:
- Prelude as
std/prelude/directory. Move the inlinePRELUDE_SOURCEraw-string constant incrates/gruel-compiler/src/unit.rs:90-316(~225 lines of Gruel embedded in Rust) onto disk understd/prelude/, with a per-topic file split (option.gruel,result.gruel,char.gruel,string.gruel,interfaces.gruel,target.gruel,cmp.gruel). Names declared in any prelude file are auto-imported into user code without an@import— a small "prelude scope flattening" mechanism is added to support that. - Built-in interface declarations move to Gruel.
Drop,Copy,Clone,Handle(currentlyBuiltinInterfaceDefrecords ingruel-builtins/src/lib.rs:945-1015) becomepub interfacedeclarations instd/prelude/interfaces.gruel. The compiler keeps its hardcoded behavior (drop glue at scope end,@derive(Copy)validation,@derive(Clone)synthesis,Handlelinearity carve-out) keyed off interned names — same pattern as how it recognizesOptionandResulttoday. - Built-in enum declarations move to Gruel.
Arch,Os,TypeKind,Ownership(ingruel-builtins/src/lib.rs:657-717) becomepub enumdeclarations instd/prelude/target.gruel. The intrinsics that produce values of these types (@target_arch,@target_os,@type_info,@ownership) switch from variant-by-index lookup to variant-by-name lookup against the Gruel-defined enum. - Eq, Ord, and operator desugaring. Add
pub interface Eq,pub interface Ord, andpub enum Orderingtostd/prelude/cmp.gruel. Sema's binary-operator analyzer gains a fall-through path: when operands are not built-in primitives, look upEq/Ordconformance and desugar==,!=,<,<=,>,>=to method calls oneq/cmp. This is real operator overloading — the language gains it as a side effect of moving stdlib types out of Rust.
Explicitly deferred to a follow-up ADR. This ADR does not touch String or Vec(T). ADR-0072's runtime-collapse target (~750 LOC of gruel-runtime/src/string.rs shrinking to a UTF-8-validation core) is real and tracked, but landing it cleanly depends on (a) operator overloading existing for non-built-in types, and (b) the prelude being a directory module rather than a string. This ADR delivers (a) and (b); a sibling ADR consumes them.
LOC impact. ~250 Rust LOC removed (~415 deletions, ~165 additions for new operator-dispatch logic and the prelude loader), ~285 Gruel LOC added. The win is modest by line count; the structural value is the language gains operator overloading and the stdlib gets a real foundation, both of which unlock the larger String/Vec collapse later.
Context
Where things sit today
Stdlib. std/_std.gruel (re-exports math) and std/math.gruel (abs, min, max, clamp) — 30 LOC of Gruel total, plus the resolution machinery in crates/gruel-air/src/sema/analysis.rs:5620 (resolve_std_import). @import("std") works; GRUEL_STD_PATH and std/-relative search are wired.
Prelude (the inline string). crates/gruel-compiler/src/unit.rs:90 defines const PRELUDE_SOURCE: &str = r#"…"#; — 225 lines containing Option(T), Result(T, E), char__from_u32, char__is_ascii, char__len_utf8, char__encode_utf8, Utf8DecodeError, String__from_utf8, String__from_c_str. Loaded under FileId::PRELUDE before user code (unit.rs:526); names are visible without @import. The mechanism that makes them globally visible is not the standard module re-export pattern — FileId::PRELUDE's top-level items go straight into the global resolution table.
Built-in interfaces. gruel-builtins/src/lib.rs:945-1015, registered into BUILTIN_INTERFACES:
Drop(ADR-0059) — method-presence conformance;fn drop(self)makes a type conform.Copy(ADR-0059) —@derive(Copy)only; compiler emits a bitwise copy; method body is never user-written.Clone(ADR-0065) —@derive(Clone)synthesizes a recursive clone; rejected on linear types.Handle(ADR-0075) — method-presence conformance; permitted on linear types.
Sema injects them at crates/gruel-air/src/sema/builtins.rs:145 (inject_builtin_interfaces). The declarations are pure data; the compiler's behavior is hardwired by name — Option and Result follow the same recognize-by-name pattern, but their declarations are already in the prelude, so the precedent for moving the declarations exists.
Built-in enums. gruel-builtins/src/lib.rs:657-717, registered into BUILTIN_ENUMS:
Arch(8 unit variants) used by@target_arch()Os(5 unit variants) used by@target_os()TypeKind(7 unit variants) used by@type_info()Ownership(3 unit variants) used by@ownership(T)
The intrinsics today materialize values by selecting variant by index against BuiltinEnumDef::variants. Sema stores special IDs (builtin_arch_id etc.) on Sema for fast lookup.
Operators on user types. Currently impossible. There is no Eq or Ord interface in the language. The only types with overloaded operators are:
- Numeric primitives (
i32 + i32, etc.) — direct sema analysis, no dispatch. bool == bool— direct sema analysis.String— registry-driven viaBuiltinTypeDef::operators(gruel-builtins/src/lib.rs:345-376), routing to runtime__gruel_str_eq/__gruel_str_cmp.
A user-defined struct cannot overload == today; users must call an explicit method. This is the largest missing piece in the language for ergonomic stdlib types — and the load-bearing reason this ADR exists rather than just relocating registry data.
Why now
- The on-disk stdlib mechanism is stable (ADR-0026 stable since 2026-01-04) but underused:
std/math.gruelis the only customer. Result(T, E)(ADR-0070) andchar(ADR-0071) prove the "Gruel-resident generic types in prelude" pattern works.- The inline
PRELUDE_SOURCEis approaching the size where it resists edits (no syntax highlighting, awkward escaping). - Operator overloading has been blocked behind "we'll figure it out when we need it." This ADR needs it for the stdlib types it adds, so we figure it out now — minimally, just
EqandOrd. - The bigger
String/Vecmigration (ADR-0072 anticipates ~490 LOC ofstring.rsretiring) is gated on operator overloading existing for non-built-in types. Shipping this ADR clears the path.
What this ADR does not do
- Does not move
Stringmethods to Gruel. The 31no_mangleextern functions ingruel-runtime/src/string.rs(751 LOC) all stay. TheSTRING_TYPEregistry entry stays.String's 6 registry-driven operator entries stay (and continue to win over the new Eq/Ord dispatch — see Decision §4). - Does not move
Vec(T)to Gruel.Vec(T)stays asBuiltinTypeConstructorKind::Vecwith codegen-inlined methods. - Does not retire
__gruel_str_eq/__gruel_str_cmp. They keep being called via the existingBUILTIN_TYPESoperator-routing path. - Does not add
PartialEq/PartialOrd. Floats keep their primitive comparisons; the Eq/Ord interfaces are for non-float types only (see §4). - Does not add interface bounds on generics. Gruel's comptime generics are structural/duck-typed; once a method exists, monomorphization picks it up. Adding
T: Ordsyntax is a separate ADR.
These are real follow-ups, not shrugs. The next ADR (call it 0079) will consume what this one ships.
Decision
Four shifts, executed as separate phases.
Shift 1: Prelude as std/prelude/ directory module
Replace the inline PRELUDE_SOURCE string with a real on-disk tree under std/prelude/, loaded automatically before user code.
Layout.
std/
_std.gruel # existing
math.gruel # existing
_prelude.gruel # NEW — manifest listing prelude submodules
prelude/
option.gruel # Option(T)
result.gruel # Result(T, E)
char.gruel # char__from_u32, char__is_ascii, char__len_utf8, char__encode_utf8
string.gruel # Utf8DecodeError, String__from_utf8, String__from_c_str
interfaces.gruel # Drop, Copy, Clone, Handle (Shift 2)
target.gruel # Arch, Os, TypeKind, Ownership (Shift 3)
cmp.gruel # Eq, Ord, Ordering (Shift 4)
Auto-import via prelude-scope flattening. The standard @import("std") resolution returns a struct; you'd write prelude.option.Some. The prelude needs unqualified names. This is a new behavior, not a relocation — the current inline prelude works because all its declarations are top-level under one synthetic FileId::PRELUDE.
The cheap way to preserve this: when the loader walks std/prelude/, every .gruel file there is parsed and its top-level pub items are merged into a single virtual prelude scope under FileId::PRELUDE (or a small range of prelude-flagged ids). _prelude.gruel is a manifest — either a literal list of files (pub const _ = @include_prelude("option.gruel"); ...) or implicit (every .gruel file in prelude/ is included). Implicit-by-discovery is simpler; pick that unless ordering issues surface.
Loading. CompilationUnit::parse() (crates/gruel-compiler/src/unit.rs:523-541) currently constructs the prelude as SourceFile::new("<prelude>", PRELUDE_SOURCE, FileId::PRELUDE). Replace with: locate std/prelude/ via the same GRUEL_STD_PATH / relative-std/ machinery resolve_std_import uses, parse each .gruel file under it as FileId::PRELUDE, and prepend the merged AST to the user files.
Fallback. Keep a PRELUDE_FALLBACK map in Rust mirroring the on-disk files via include_str! for tests, missing-stdlib hosts, and binary distribution. The disk is the source of truth; the embedded copy is a safety net.
FileId discipline. Either reuse FileId::PRELUDE for every prelude file, or add an is_prelude(file_id) predicate. The choice affects ADR-0073's privileged-access carve-out — pick whichever keeps that one-liner unchanged.
Shift 2: Built-in interface declarations → Gruel
Move Drop, Copy, Clone, Handle declarations into std/prelude/interfaces.gruel:
pub interface Drop {
fn drop(self);
}
pub interface Copy {
fn copy(borrow self) -> Self;
}
pub interface Clone {
fn clone(borrow self) -> Self;
}
pub interface Handle {
fn handle(borrow self) -> Self;
}
(Surface syntax to verify against ADR-0056 during Phase 2; if the keyword is iface or the receiver-mode marker differs, adjust verbatim.)
Compiler changes. Sema looks up the four interfaces by interned name from the prelude scope rather than from BUILTIN_INTERFACES. The hardcoded behavior — drop glue at scope end (ADR-0010), @derive(Copy) field-by-field validation, @derive(Clone) recursive-clone synthesis, Handle linearity carve-out (ADR-0075) — stays in Rust, keyed off the interface name.
Deletions. BUILTIN_INTERFACES, DROP_INTERFACE, COPY_INTERFACE, CLONE_INTERFACE, HANDLE_INTERFACE, BuiltinInterfaceDef, BuiltinInterfaceMethod, BuiltinIfaceTy, BuiltinInterfaceConformance (~80 LOC), plus inject_builtin_interfaces at crates/gruel-air/src/sema/builtins.rs:145 (~30 LOC).
Shift 3: Built-in enum declarations → Gruel
Move Arch, Os, TypeKind, Ownership into std/prelude/target.gruel:
pub enum Arch { X86_64, Aarch64, X86, Arm, Riscv32, Riscv64, Wasm32, Wasm64 }
pub enum Os { Linux, Macos, Windows, Freestanding, Wasi }
pub enum TypeKind { Struct, Enum, Int, Bool, Unit, Never, Array }
pub enum Ownership { Copy, Affine, Linear }
Intrinsic adjustment. @target_arch, @target_os, @type_info, @ownership switch from variant-by-index lookup against BuiltinEnumDef::variants to variant-by-name lookup against the Gruel-defined enum interned in the prelude. The variant-name → variant-index mapping is computed once at type interning. Side benefit: variants can be reordered in the Gruel source without breaking intrinsic codegen.
Deletions. BUILTIN_ENUMS, ARCH_ENUM, OS_ENUM, TYPEKIND_ENUM, OWNERSHIP_ENUM, BuiltinEnumDef (~80 LOC), plus the builtin_arch_id / builtin_os_id / builtin_typekind_id / builtin_ownership_id fields on Sema and the corresponding injection loop.
Shift 4: Eq, Ord, and operator desugaring
Add to std/prelude/cmp.gruel:
pub enum Ordering { Less, Equal, Greater }
pub interface Eq {
fn eq(borrow self, borrow other: Self) -> bool;
}
pub interface Ord {
fn cmp(borrow self, borrow other: Self) -> Ordering;
}
Compiler changes (the load-bearing piece). The binary-operator analyzer in sema gains a fall-through path:
Given `a OP b` where OP ∈ { ==, !=, <, <=, >, >= }:
1. If both operands are built-in numeric primitives:
use the existing primitive-op path. (Unchanged.)
2. Else if both operands are bool and OP ∈ {==, !=}:
use the existing primitive-op path. (Unchanged.)
3. Else if `typeof(a)` is in BUILTIN_TYPES and has a registry operator entry:
use the registry path. (Unchanged — String keeps working.)
4. Else if OP ∈ {==, !=} and typeof(a) conforms to Eq:
desugar to `a.eq(other: b)` (and `!` for !=).
5. Else if OP ∈ {<, <=, >, >=} and typeof(a) conforms to Ord:
desugar to `match a.cmp(other: b) { … }` against Ordering variants.
6. Else: type error.
Steps 4–5 are new. Steps 1–3 are the existing analyzer untouched.
Eq / Ord recognition is by interned name from the prelude — same recognize-by-name pattern as Drop / Copy / Clone / Handle / Option / Result. Conformance is structural per ADR-0056: a type conforms to Eq if it has a method fn eq(borrow self, borrow other: Self) -> bool.
Float disposition. f32 and f64 keep primitive == / != / < / etc. via step 1. They do not conform to Eq or Ord automatically — adding partiality (NaN handling) is PartialEq / PartialOrd territory and out of scope. If a user wants to put a float in a generic slot that requires Ord, they'll get a clear "f64 doesn't implement Ord — use a wrapper or write a partial-comparison function" error.
Existing String operators are unaffected. Step 3 wins before step 4 ever runs. String's 6 registry-driven operator entries keep routing to __gruel_str_eq / __gruel_str_cmp. Future ADR can give String eq / cmp methods, drop the registry entries, and let it fall through to step 4.
Comptime monomorphization. Gruel's comptime generics are structural — a body like fn max(comptime T: type, a: T, b: T) -> T { if a < b { b } else { a } } typechecks at instantiation if < resolves for T. Once < desugars through Ord::cmp, T needs to provide a cmp method. Today this monomorphization ergonomics is unchanged; users gain the option of relying on Ord conformance.
Implementation cost. ~50–80 Rust LOC of new dispatch in the binop analyzer; ~30 Gruel LOC for the cmp.gruel file.
Net Rust-LOC budget
| Phase | Rust LOC removed | Rust LOC added | Gruel LOC added |
|---|---|---|---|
1. Prelude as std/prelude/ | ~225 (string literal) | ~50 (loader + fallback + flatten) | ~225 (file move) |
| 2. Interfaces → Gruel | ~110 (registry + injection) | ~5 (name lookup) | ~20 |
| 3. Built-in enums → Gruel | ~80 (registry + special ids) | ~30 (variant-by-name in intrinsics) | ~10 |
| 4. Eq/Ord + operator dispatch | — | ~80 (sema dispatch) | ~30 |
| Total | ~415 | ~165 | ~285 |
Net: ~250 Rust LOC out, ~285 Gruel LOC in, plus operator overloading for non-built-in types as a permanent language win.
Implementation Phases
Each phase ships independently behind the stdlib_mvp preview gate, ends with make test green, and quotes its own LOC delta in the commit message.
Phase 1: Prelude as std/prelude/ directory
- Create
std/prelude/{option,result,char,string}.gruel, splitting the currentPRELUDE_SOURCEcontent by topic. - Add
PRELUDE_FILESmap incrates/gruel-compiler/src/prelude_source.rsmirroring the on-disk files viainclude_str!. - Implement prelude-scope flattening: prelude files are concatenated into a single virtual source parsed under
FileId::PRELUDE— preserves the existing top-level-items-go-global behavior unchanged. - Modify
CompilationUnit::parse()to load viaassemble_prelude_source(disk first viaGRUEL_STD_PATHor upward search; embedded fallback on miss). - ADR-0073's
is_accessiblecarve-out continues to work because all prelude files shareFileId::PRELUDE(concatenated into one virtual file). -
Sema-direct test fixtures continue to work because the embedded fallback is always available. - Delete the
PRELUDE_SOURCEconstant. - All 2073 spec tests + 89 UI tests pass.
Phase 2: Built-in interfaces → Gruel
- ADR-0056 surface syntax verified:
interface Name { fn method(self...) -> RetType; }with receiver modesself,self: Self,self: Ref(Self),self: MutRef(Self). - Created
std/prelude/interfaces.gruelwithDrop,Copy,Clone,Handledeclarations. - Removed
inject_builtin_interfacesfromgruel-air/src/sema/builtins.rs. Interface declarations now flow through standardresolve_declarations; conformance still keys off interned names ("Copy","Drop","Clone"). - Deleted
BUILTIN_INTERFACES,DROP_INTERFACE,COPY_INTERFACE,CLONE_INTERFACE,HANDLE_INTERFACE,BuiltinInterfaceDef,BuiltinInterfaceMethod,BuiltinIfaceTy,BuiltinInterfaceConformancefromgruel-builtins/src/lib.rs. Kept a smallBUILTIN_INTERFACE_NAMESstatic for breadcrumbs. - Replaced doc-generator iteration over
BUILTIN_INTERFACESwith static text;make gen-builtins-docsandmake check-builtins-docsclean. - All 2073 spec tests + 89 UI tests pass.
Phase 3: Built-in enums → Gruel
- Created
std/prelude/target.gruelwithArch,Os,TypeKind,Ownership. Variant order preserved to match the existing compiler-side mappers (arch_variant_index,os_variant_index). - Kept the index-based mappers — they encode an ordering invariant the prelude file matches; intrinsics build
EnumVariant { enum_id, variant_index }directly. TheEnumIds come from a newcache_builtin_enum_idsstep run afterresolve_declarations(so the prelude's enum decls have been registered). - Deleted
BUILTIN_ENUMS,ARCH_ENUM,OS_ENUM,TYPEKIND_ENUM,OWNERSHIP_ENUM,BuiltinEnumDef,get_builtin_enum,is_reserved_enum_namefromgruel-builtins/src/lib.rs(~80 LOC). KeptBUILTIN_ENUM_NAMESfor breadcrumbs. - Removed the BUILTIN_ENUMS injection loop from
inject_builtin_types; kept thebuiltin_*_idcache fields, populated incache_builtin_enum_ids. - Added
pub prepend_prelude(ast, interner, preview_features)helper for tests/callers that bypassCompilationUnit::parse. - Updated the doc generator to use static text instead of iterating over the deleted registry.
- All 2073 spec tests + 89 UI tests pass;
test_target_arch_intrinsic_uses_compile_targetupdated to callprepend_prelude.
Phase 4: Eq, Ord, and operator desugaring
- Created
std/prelude/cmp.gruelwithpub enum Ordering { Less, Equal, Greater },pub interface Eq,pub interface Ord. Receiver/parameter shape:fn eq(self: Ref(Self), other: Self) -> bool;/fn cmp(self: Ref(Self), other: Self) -> Ordering;. (Selfin nested type position likeRef(Self)for non-receiver params isn't currently accepted by the resolver —other: Selfis the workable shape, by-value.) - Cached
builtin_ordering_idvia the existingcache_builtin_enum_idsstep soOrddesugaring can constructOrdering::Less/Ordering::Greaterenum-variant AIR refs without paying a name-lookup at every</>call site. - Implemented binop dispatch fall-through in
analyze_comparison(crates/gruel-air/src/sema/analysis.rs):- Numeric / bool / char / unit primitives → existing
Binpath (unchanged). - Built-in
String→ existingBinpath (registry routes via__gruel_str_eq/__gruel_str_cmp, unchanged). - NEW: User struct or enum with
eqmethod → desugar==/!=to a method call returningbool.!=wraps inBin(Ne, call, true). - NEW: User struct or enum with
cmpmethod → desugar</<=/>/>=to a method call returningOrdering, then compare againstOrdering::Less/Ordering::Greater. Mappings:<→cmp == Less,>=→cmp != Less,>→cmp == Greater,<=→cmp != Greater. - User struct without
cmpmethod on ordering ops → clear "does not conform toOrd" error naming the missing method. (Without the dispatch this path was a generic type mismatch.) - User struct without
eqmethod on==/!=→ existingbuild_value_eq(bitwise field-by-field) path, preserving backward compatibility. Defining aneqmethod opts the type into custom equality.
- Numeric / bool / char / unit primitives → existing
- End-to-end verification: a scratch program with a
Pointstruct exercising all six operators (==,!=,<,<=,>,>=) returned the expected exit code (each operator added a distinct power-of-two contribution; final = 63 confirms all six dispatched correctly). Primitive operators still work (no regression). All 2073 spec tests + 89 UI tests pass. - Helpers:
Sema::lookup_user_method(ty, method_sym)for struct/enum method lookup;Sema::finish_operator_dispatch(...)for shared post-call comparison construction.
Phase 5: Stabilization
- No
stdlib_mvppreview gate was added (none of the four shifts changed user-visible language behavior in a way that needed staging — declarations are additive, registry deletions are internal). - ADR status updated to Implemented.
- Generated docs swept:
docs/generated/builtins-reference.mdregenerated with the static interface and enum sections;docs/generated/intrinsics-reference.mdis registry-driven and unaffected. No references to deletedBUILTIN_INTERFACESorBUILTIN_ENUMSremain (one comment ingruel-air/src/sema/analysis.rsmentionsARCH_ENUM/OS_ENUMhistorically as the source of variant order — now sourced fromstd/prelude/target.gruel; left as-is for archaeology).
Consequences
Positive
- Operator overloading lands in the language. Every user-defined and stdlib-defined struct can now do
==and<. Permanent ergonomic win. - Prelude becomes a normal source file tree. Syntax highlighting, line-level diffs, per-topic files. Adding to it stops requiring escaping.
- Stdlib gains substance.
std/prelude/houses 7 Gruel files of declarations the compiler used to embed in Rust. Future stdlib growth (std/io,std/collections) follows the same path. - Reorderable enum variants. Once
Arch/Os/TypeKind/Ownershipare name-resolved, contributors can reorder for readability without touching intrinsic codegen. - Structural
String/Veccollapse becomes feasible. The next ADR can assume operator overloading exists and the prelude is a directory; the eventual collapse stops needing special-case operator routing. - Lower contributor barrier for declaration changes. Adding an interface, a target-platform variant, or a prelude function becomes "edit a Gruel file" instead of "edit a Rust registry, a sema injector, and the generated docs."
Negative
- Prelude-scope flattening is new behavior. It's a small mechanism (~30 LOC of file-discovery + per-file parse + scope merge), but it's new — not a relocation. If it has bugs, every program is affected. Mitigated by Phase 1 carrying the same content currently inlined; the loaded behavior should match exactly.
- Operator desugaring adds an analysis path. Steps 4–5 of the binop analyzer can fail in new ways (e.g. one operand
Eq-conforming, the other not). Error messages need to nameEq/Ordclearly. ~80 Rust LOC of new sema is small but warrants UI tests. Orderingis now a load-bearing prelude type. A user shadowingOrderingwould break operator desugaring. Same risk profile asOption/Resulttoday; not a new class of problem.
Neutral
Stringkeeps its registry operators. Step 3 of the binop dispatch wins before step 4 ever runs.__gruel_str_eq/__gruel_str_cmpkeep being called.String/Vecruntime untouched. All 31 functions ingruel-runtime/src/string.rsand the codegen-inlined Vec methods stay. The follow-up ADR consumes this ADR's deliverables.- No spec changes for existing surfaces.
Option's,Result's,String's,Vec's, and the four interfaces' observable behavior is unchanged. - No new feature flags surface to users.
stdlib_mvpexists only for internal staging.
Open Questions
std/prelude/vs siblingprelude/? This ADR picksstd/prelude/for resolution-path reuse. Alternative: keep prelude resolution distinct so a user replacingstd/for a freestanding target doesn't lose the prelude. Resolve during Phase 1; the directory shape is the same either way.- Manifest vs implicit discovery. Does
_prelude.gruellist the files inprelude/explicitly, or is every.gruelfile underprelude/implicitly part of the prelude? Implicit is simpler; explicit gives a single point of truth. Tilt toward implicit unless ordering issues surface. - Variant-by-name lookup at intrinsic codegen. Phase 3 hinges on the compiler being able to look up an enum's variant by interned name. Verify this is supported (it is for
Option::Someetc.); if not, Phase 3 needs a small helper. - Conformance check ordering. Step 4 of the binop dispatch (Eq fallback) only runs if step 3 (BUILTIN_TYPES registry) misses. Verify that the registry check is cheap (a hashmap lookup) so the new path doesn't slow down existing programs that hit step 1 or step 2.
PartialEq/PartialOrdfor floats. This ADR ducks the question. If a downstream user wants generic code that includes floats, they'll need something. Probably a follow-up ADR addingPartialEq/PartialOrdand either re-routing primitive float comparisons through them or keeping the dual track. Not blocking.
Future Work
String/Vecruntime collapse (next ADR). Move the 30+ String runtime functions into Gruel asself.bytes.method()compositions; eventually dropSTRING_TYPEfromBUILTIN_TYPES. ReformulateVec(T)as a comptime-generic struct calling@alloc/@realloc/@free. The win that ADR-0072 anticipated (~490 LOC ofstring.rsretiring) plus ~300 LOC of Vec codegen-method-lowering. Now feasible because operator overloading exists for non-built-in types and the prelude is a directory.- Operator desugaring for
Stringvia Eq/Ord. Once the next ADR givesStringeq/cmpmethods, the BUILTIN_TYPES operator entries retire and step 3 of the binop dispatch goes away. ~80 more Rust LOC out. PartialEq/PartialOrdfor floats. Land separately if needed.- Interface bounds on generics. Currently structural / duck-typed at comptime. Adding
T: Ordsyntax with explicit checking is a separate ADR. std/io,std/process,std/env. With the stdlib mechanism warm, these are the next obvious surfaces.std/collections. OnceVec(T)is Gruel-defined,HashMap/BTreeMapbelong here.
References
- ADR-0010: Destructors — Drop-glue auto-synthesis (relied on for Shift 2)
- ADR-0020: Built-in Types as Synthetic Structs — Synthetic-struct mechanism that this ADR partially retreats from for interfaces and enums
- ADR-0026: Module System — Stdlib resolution mechanism reused by Shift 1
- ADR-0050: Centralized Intrinsics Registry — Pattern model: hardcoded enum + registry, drop entries to relocate behavior
- ADR-0056: Structural Interfaces — Interface surface syntax for Shifts 2 and 4
- ADR-0059: Drop and Copy Interfaces — Interface behaviors that stay hardwired by name
- ADR-0065: Clone and Option — "Gruel-resident generic enum in prelude" pattern
- ADR-0070: Result Type — Same pattern, expanded
- ADR-0071: char Type — "Prelude functions for built-in scalar methods" pattern
- ADR-0072: String as Vec(u8) Newtype — Direct precursor; the runtime collapse it anticipated is the follow-up ADR enabled by this one
- ADR-0073: Field/Method Visibility — Privileged-access carve-out for prelude/stdlib code
- ADR-0075: Handle Interface —
Handledeclaration moves in Shift 2