Skip to main content
Next (unreleased) · docs c218d875 · core f63c1e69 · built 2026-10-26 22:39 UTC

Limitations and Gaps

Every known limitation of the implementation (LIMIT-*, on the Architecture Spec) and every gap in the language specification (GAP-*, on the Language Spec) in one list, including resolved and superseded records. The list is built from the records when the site is built; nothing here is written by hand.

A limitation is where the implementation behaves differently from what the current Language Spec says; a gap is where the spec itself is silent, ambiguous or unfinished (ADR-0057). For the counts and the requirements behind them, see Architecture Health and Language Spec Health.

41 limitations, 25 gaps: 40 open.

knowngapdeclarationsGAP-DECLARATIONS-001 Tuples cannot implement an aspect. A tuple cannot implement an aspect; use a named struct. An anonymous record can, for a local aspect, through a record-target extend.plannedgapdeclarationsGAP-DECLARATIONS-002 Aspect implementations may not safely widen a method's generic constraints. An implementation's generic constraints must match the aspect method's exactly, even where a looser signature would be safe.knowngapdeclarationsGAP-DECLARATIONS-003 No Ord or Hash aspect exists, so no type has ordering or hashing impls. Neither Ord nor Hash exists in std::core, so no type, arrays included, has an ordering or hashing implementation.knowngapexpressionsGAP-EXPRESSIONS-001 A record rest pattern cannot borrow the remainder through a reference. No form binds the remainder of a record through a reference: a borrowed view of "everything but these fields" has no spelling.knowngapfunctionsGAP-FUNCTIONS-001 Free-function overloading is implemented but the Language Spec does not describe it. A module can declare several free functions with one name and the compiler resolves calls by exact argument types, but the spec states no rules for it, including whether an overloaded name can be used as a value. Architecture limitation: LIMIT-TYPE-CONSTRUCTION-002 ↗plannedgapfunctionsGAP-FUNCTIONS-002 Callable<A, B> does not exist, so a function type cannot be bounded by an aspect. Callable<A, B> is not defined in std::core, so a function or closure type cannot satisfy an aspect bound and dyn Callable is unavailable.plannedgapfunctionsGAP-FUNCTIONS-003 Written function types are move-only, with no copy qualifier or Erased state yet. A written function type is always move-only; the copy |T| -> U qualifier and the Erased (capability unknown) state are not available.knowngapfunctionsGAP-FUNCTIONS-004 ? is defined for the core Result and Option only; there is no Try aspect. ? works on the core Result and Option and on no other type; a user-defined type cannot be propagated with ?. A future Try aspect would generalise the rule.knowngapmodulesGAP-MODULES-001 Module graphs may not be cyclic, so modules cannot be mutually dependent. A circular import is a compile error, so two modules cannot depend on each other. Architecture limitation: LIMIT-EVALUATION-002 ↗acceptedgapmodulesGAP-MODULES-002 A directory cannot be a module on its own: there is no mod.mtl equivalent. There is no mod.rs equivalent: a directory is not a module by itself, so name/mod.mtl has no meaning; the facade is a name.mtl file beside the name/ directory.knowngapmodulesGAP-MODULES-003 Top-level let and var bindings cannot be exported. A top-level let or var is always module-private; there are no public value exports.knowngapownershipGAP-OWNERSHIP-001 No borrow checking: reference exclusivity and duration are not enforced. Nothing prevents a shared and an exclusive reference to the same place from coexisting.plannedgapownershipGAP-OWNERSHIP-002 A deeper move does not narrow the enclosing value's row. Moving root.a narrows root's type, but moving root.a.b does not: a record-typed field moves as a unit, so the type checker still treats root as whole.plannedgapownershipGAP-OWNERSHIP-003 A field cannot be moved out of a Drop type, even where its destructor would not need it. Moving a field out of a Drop type is always rejected; row-bounded Drop dispatch would allow it where the destructor does not need that field.knowngaptypesGAP-TYPES-001 No higher-rank polymorphism. A function parameter is a monotype, so it cannot be used at two types in one body.knowngaptypesGAP-TYPES-002 The storage model of List<T> and its boundary with T[] and [T; N] is not specified. How a growable List<T> allocates and grows its storage, and where it ends and T[] and [T; N] begin, is not fully specified.knowngaptypesGAP-TYPES-005 No spelling for an operator on a generic parameter. An operator such as + or < on a bare type parameter is rejected, and no aspect exists that grants it.knowngaptypesGAP-TYPES-008 A record literal accepts at most one row spread. No form combines the fields of two rows into one record: merging two generic records has no spelling.knowngaptypesGAP-TYPES-009 Tuples have no rest pattern or spread over their numeric-label row. A tuple cannot be split into its first element and the rest, or extended, in a generic body.acceptedgaptypesGAP-TYPES-010 A function cannot return an open record while hiding its extra fields. An open record with an anonymous tail is rejected as a return type, so an API cannot promise only some fields of the record it returns.acceptedgaptypesGAP-TYPES-011 A written function type cannot say it accepts any record with at least these fields. An open record with an anonymous tail is rejected inside a function type, and a named row fixes the row for the whole signature, so no written type means 'works for every wider record'.acceptedgaptypesGAP-TYPES-012 A record or struct field cannot have an open-record type with an anonymous tail. An anonymous open record is rejected as a field type, so a field cannot hold 'any record with at least these fields'.knownlimitationcoherenceLIMIT-COHERENCE-001 extend<record T: {...}> T: Aspect is accepted, then never applies. A blanket aspect impl bounded by a row (extend<record T: { x: f64, .. }> T: A) passes coherence, but a call through the bound never dispatches to it; the same class of mistake is rejected outright for the equivalent non-generic form.knownlimitationelaborationLIMIT-ELABORATION-001 ElaboratedModuleGraph's field is fully public, unlike NormalizedModuleGraph's. ElaboratedModuleGraph's field is public, so code outside the frontend can build one without running elaboration, unlike NormalizedModuleGraph.acceptedlimitationevaluationLIMIT-EVALUATION-001 Generic function dispatch re-constructs on every call. A generic function's body is re-constructed at every call instead of once per instantiation.knownlimitationevaluationLIMIT-EVALUATION-003 RuntimeRegistry is not fully covered by the resolution-freeze checker. Two runtime registry maps are name-keyed by design and sit outside the check that forbids name lookups after resolution.knownlimitationevaluationLIMIT-EVALUATION-004 The evaluator is a deliberate PoC, not the target implementation shape. The tree-walking evaluator is a proof of concept expected to be replaced, not a stable target shape.plannedlimitationevaluationLIMIT-EVALUATION-005 Destructor invocation is not implemented; only empty drop bodies are accepted. Drop bodies never run, so only an empty drop body is accepted.plannedlimitationevaluationLIMIT-TYPES-003 Record Display is a runtime built-in, not Metel code. Display for a record of Display fields is a built-in in the evaluator, a stand-in for RFC-0174's comptime for; no user aspect, and no Eq/Hash, can walk a record's fields; Clone is likewise a built-in.acceptedlimitationmove-checkLIMIT-MOVE-CHECK-001 Closure move-check is always-on while general move-check stays opt-in. Closure move checks always run, while the general move checker stays behind --move-check.knownlimitationpath-normalizationLIMIT-PATH-NORMALIZATION-001 root::/super:: are rejected in expression position when the target is declared directly in that module. root::name and super::name work through a submodule path but are rejected in expression position when name is declared directly in the root or parent module.acceptedlimitationresolutionLIMIT-RESOLUTION-001 Resolved-identity model is in-memory only. Resolved identities exist only in memory; nothing persists them across processes.knownlimitationresolutionLIMIT-RESOLUTION-002 Inference-time type lookup falls back to a name-approximate, cross-module search. Inference falls back to a name-based, cross-module type lookup where the declaration id is missing, which can confuse same-named types.knownlimitationtype-constructionLIMIT-TYPE-CONSTRUCTION-001 Call::callee_id falls back to name dispatch for first-class functions. callee_id is empty for methods, nested functions and first-class function calls, which fall back to name dispatch.knownlimitationtype-constructionLIMIT-TYPE-CONSTRUCTION-002 Overload sets are not exportable across modules. Overload sets are seeded by two different mechanisms; exporting them across modules (METEL-188) is future work. Language gap: GAP-FUNCTIONS-001 ↗knownlimitationtype-constructionLIMIT-TYPE-CONSTRUCTION-003 == and != reject non-primitive operands instead of dispatching through Eq. == and != reject operands that are not numbers, booleans, strings or chars; call .eq(..) instead.knownlimitationtype-inferenceLIMIT-TYPE-INFERENCE-002 Field access needs a concrete receiver type at the access site. A field access on an unannotated parameter is rejected; add a type annotation.knownlimitationtype-inferenceLIMIT-TYPE-INFERENCE-003 .. is not supported in an anonymous record pattern. A pattern ending in .. is rejected for a concrete anonymous record; name every field.knownlimitationtype-inferenceLIMIT-TYPE-INFERENCE-006 Type checking is sequential by construction: one shared generator, one export generator, Rc state. Inference threads one &mut generator through every pass and one export generator through every module, and its context uses Rc, so modules cannot be checked in parallel.knownlimitationtype-inferenceLIMIT-TYPE-INFERENCE-007 Type checking time grows super-linearly with the number of functions in a module. Two confirmed O(n) per-function costs (metel-core#1232) cut an 8,000-function module from ~79s to ~6.6s; a smaller super-linear residual remains, unprofiled.

Showing 40 of 66.