RFC dan proposal mencatat rancangan. Untuk kemampuan yang benar-benar dapat dipakai, lihat matriks kapabilitas runtime dan catatan rilis.
RFC 0001: Core Types, Nullability, and Errors
Status: Accepted
Areas: Appendix G.1, G.3, G.5, G.6, G.10; Appendix H.4
Depends on: RFC 0002 for ownership and representation rules
Decision: Accepted by the project owner on 2026-10-23. This approves the design direction; implementation and conformance evidence remain outstanding.
Implementation status: Partial. The compiler supports product shape values with built-in, shape, and tagged-variant fields, including forward declarations, dependency-ordered C layouts, positional construction, checked field access, field updates through mutable var values, and function parameter/return use. Tagged variant declarations support unit cases and named typed payloads, Type::Case unit constructors and positional Type::Case(...) payload constructors, exhaustive indented block matching, multiple payload bindings, and use as fields or payloads of Option[T] and Result[T, E]. Guarded numeric match and exhaustive variant/Option/Result match support direct function returns, typed let/var initialization, and assignment to an existing mutable variable via set; subjects are evaluated once and each arm is checked against its destination type. Numeric guards require a final else; sum-type matches require every declared case. Non-void functions require a return on every path through the supported block and inline if/elif/else forms, including nested chains, and through exhaustive statement-form matches whose arms all return; loops do not prove a return, and a nested return match does not falsely satisfy an outer conditional path. var plus set label = ... supports the assignment-based conditional style alongside early returns and match. The previous 41/41 self-host suite passed at the preceding language-core milestone, and its dedicated flow test runs under ASan/UBSan. Option[T] and Result[T, E] support nested built-in and declared shape/variant payloads, bounded to 16 constructor levels, checked accessors, same-type postfix ?, and direct explicit From[Source] for Target conversions for ? in let, var, set, and return. A concrete implementation has one fn from(value: Source) -> Target body; duplicate source/target pairs are rejected. Payload copies use the bounded runtime arena. Match values in arbitrary nested expression positions, general-purpose generic type constructors/functions and traits, conversion chaining, arbitrary nested ? expressions, derives, and source-level ownership checking are not implemented, so this RFC's acceptance criteria are not met.
2026-10-27 collection update: Concrete built-in List[T] values now provide list_count, list_push, list_get, list_set, and safe list_try_get returning Option[T]. Safe lookup covers TextList, primitive values, shapes, variants, and nested supported payload types; negative and out-of-range indexes return none. for item in values: iterates both TextList and supported List[T] values; it evaluates the collection once and captures its starting length, so appended elements are not visited in the current loop. This remains a bounded collection slice, not general generic functions, traits, or slicing. See tests/typed_list.test.yz and examples/list_safe_access.yz.
Summary
Define a small statically checked type system for application code: non-null values by default, explicit optional values, product and sum types, generic Option[T] and Result[T, E], exhaustive matching, and explicit error propagation. The first release should prefer predictable semantics and useful diagnostics over implicit conversions or a large type-class system.
Motivation
The current compiler has a narrow set of built-in types and prototype slices for product shape and tagged variant types. It supports nested Option[T] and Result[T, E] wrappers around built-in, shape, and variant payloads, capped at 16 levels. Concrete direct From conversions are available for ?. Numeric and sum-type matches can return or assign their selected value in dedicated return match and set name = match contexts. Match values in arbitrary expression positions, general-purpose generics, conversion chaining, and general ? propagation remain absent. API metadata still substitutes for domain types in some places. Layered code cannot yet rely on the full type and error system proposed here.
Proposal
- Every declared value has a compile-time type. Non-null values are the default; nullable/optional data must use
Option[T]. - Add product types (
shape) and tagged sum types (variant). Fields are private by default outside their defining module; constructors and pattern matches are checked. - Add
Option[T]withsome(value)andnone, andResult[T, E]withok(value)anderr(error). Both are exhaustively matchable. - A function that can fail returns
Result;?propagates the error only when the enclosing function can return a compatibleResult. No implicit exception conversion is performed. From[A]conversion is allowed only when exactly one applicable conversion exists. If more than one is available, compilation fails and names the ambiguous conversions.- Add
Map[K, V]with stable key equality and hashing requirements. Iteration order is unspecified; an ordered map is a distinct type. Arrays/slices use bounds-checked indexing. - Newtypes are distinct at compile time and convert only through explicit constructors. JSON serialization/deserialization derives are opt-in per type and must define unknown-field, missing-field, and versioning behavior.
- Named arguments may reorder parameters, but duplicate, unknown, and missing required names are errors. Defaults are evaluated once, left to right, at the call site; a default may refer only to earlier parameters.
- Add tuples as fixed, positional product values. Keep closures expression-only in the first release; block closures, capture modes, and non-local returns require a separate proposal and tests.
- Preserve
Numberas the prototype's signed 64-bit integer during migration. Later primitive types should be explicit (Int32,Int64,UInt64,Float64,Decimal,Instant, andDuration); implicit narrowing and lossy numeric conversion are errors.Moneyis a decimal amount plus an explicit currency code, not a floating-point alias. - Add generic functions and types before trait bounds. Trait bounds must be declared and checked at compile time; dynamic dispatch is opt-in.
Illustrative syntax, subject to revision:
variant CatalogError:
Missing
InvalidInput(message: Text)
Storage(cause: StorageError)
shape Product:
id: ProductId
name: Text
price: Money
fn find_product(id: ProductId) -> Result[Option[Product], CatalogError]:
let row = database.find(id)?
return ok(row)
match find_product(id):
ok(some(product)) => show(product.name)
ok(none) => respond_not_found()
err(error) => respond_error(error)
Semantics and compatibility
- Exhaustive matching is required; adding a variant can make downstream matches fail compilation, which is an intentional source compatibility break.
?propagates only an error whose type is identical to the enclosing error type or has one unambiguous declared conversion.- No implicit null-to-default, integer truncation, or string-to-number conversion is permitted.
- The compiler must reject reads before initialization and accesses to fields absent from the declared type.
- Exact grammar, visibility defaults, recursive type layout, and inference bounds remain open until a reference grammar and conformance tests exist.
Implemented prototype slice: exhaustive Option and Result blocks
The native compiler supports block-form matches on nested Option and Result values up to 16 levels, including declared shape payloads. Nested payloads are boxed in the active program/request arena; repeated construction therefore consumes bounded arena capacity until that arena resets. Shape payloads are copied by value into arena storage and checked against their runtime shape tag when read. Each payload arm binds a local name (or _ to ignore it); none has no payload. Both variants must appear exactly once, and payload bindings are scoped to their arm. Postfix ? is supported in let, var, set, and return when the source error type matches the enclosing function's error type, or one direct concrete conversion is declared:
start:
let outcome: Result[Number, Text] = err("not found")
match outcome:
ok(value):
say number_text(value)
err(message):
say message
fn load_count(id: Number) -> Result[Number, Text]:
let count: Number = read_count(id)?
return ok(count)
Option[T] uses some(value): and none:. The match subject is evaluated once. Duplicate or missing arms are compile errors. User-defined variant declarations are supported as a prototype slice described below. A concrete direct error conversion uses this syntax:
impl From[StorageError] for ApplicationError:
fn from(error: StorageError) -> ApplicationError:
return ApplicationError::Storage("storage failed")
The implementation is compiled as a normal typed function and is called only on the error branch of ?. Both types must be concrete supported types, there must be one method with the declared signature, and duplicate source/target pairs fail. Conversions are direct and are not chained. General traits, generic From, match expressions, and ? inside arbitrary nested expressions remain unimplemented. The conversion conformance test covers imported implementations and let/var/set/return propagation under ASan/UBSan.
Implemented prototype slice: tagged variants
The native compiler accepts top-level variant Name: declarations with four-space-indented unit cases or cases containing named typed fields. Unit constructors use Name::Case; payload constructors use Name::Case(...), and all payload arguments are checked against their declared types. A block-form match uses Case: for a unit case and Case(binding, _): for a payload case. It evaluates the subject once, requires each declared case exactly once, checks the number of bindings, and scopes them to their arm. Variants can be used in function signatures, product shapes, Option[T], and Result[T, E]. Payload storage is copied into the bounded active arena; this does not provide source-level ownership or destructor guarantees.
The positive conformance example covers unit and multi-field payloads, a shape payload, option/result wrappers, and same-error ? propagation. Negative fixtures reject missing and duplicate match arms, unknown cases, constructor arity mismatches, and incompatible payload types. The example is examples/variants.yz, and the test is tests/variants.test.yz. This slice does not include enum aliases, generic variants, match values in arbitrary expression positions, visibility control, derives, or general ownership checking.
Implemented prototype slice: sum-type match values
Exhaustive variant, Option[T], and Result[T, E] matches can return a value directly or assign one to an existing mutable variable. Each arm uses ->; sum-type cases must be exhaustive, payload bindings are scoped to their arm, the subject is evaluated once, and every arm must produce the destination type. Assignment uses ordinary set and requires a previously declared mutable var:
fn describe(value: Option[Text]) -> Text:
return match value:
some(text) -> "value: " + text
none -> "empty"
fn describe_into_label(value: Option[Text]) -> Text:
var label: Text = "not assigned"
set label = match value:
some(text) -> "value: " + text
none -> "empty"
return label
tests/value_match.test.yz checks numeric and sum-type let initializers, variant/Option/Result return values, Option assignment, and rejected incomplete, duplicate, unknown, or wrongly typed arms. examples/match-forms.yz demonstrates these forms beside conditional assignment and early returns. Match is accepted in direct return, typed let/var initializer, and set statement contexts. A match nested in a call or calculation, passed directly to say, or otherwise used as an arbitrary expression is still unsupported.
Implemented prototype slice: guarded numeric matches
return match value: evaluates a Number, Int32, or Float64 subject once and checks ordered comparison guards (<, <=, >, >=, ==, or !=). A final else is required, and every arm must return a value assignable to the function return type. The same guarded form can assign into an existing mutable variable with set label = match value:; each arm is checked against that variable's declared type. This is currently statement-context lowering, not a general sum-type match expression usable inside arbitrary calls or nested expressions.
fn classify(value: Number) -> Text:
var label: Text = "not classified"
set label = match value:
< -10 -> "very negative"
< 0 -> "negative"
== 0 -> "zero"
<= 10 -> "positive"
else -> "very positive"
return label
tests/numeric_match.test.yz covers the direct-return and mutable-assignment forms for boundary values; examples/numeric_match.yz is runnable. The full RFC remains partial because general expression matches, generic types/functions, ownership, derives, and the remaining accepted criteria are not complete.
Implemented prototype slice: product shapes
Shapes currently declare one or more fields using four-space indentation. Fields may use built-in value types, nested built-in Option/Result types, or another declared shape. Forward references are allowed, and the compiler orders C struct definitions by field dependencies. Direct cyclic by-value layouts fail compilation, while Option[Shape] permits recursive links through bounded arena copies. Constructors are positional and must provide exactly one value per field in declaration order. The compiler checks each argument, rejects unknown fields at access sites, emits C structs, and supports shape values as local variables and function parameters/returns. A field can be updated with set value.field = expression only when value is a mutable var; the compiler checks that the field exists and that the assigned value has the declared type. Shapes are copied by value; pointer-containing built-in fields retain their existing runtime ownership behavior.
shape Product:
id: Number
name: Text
price: Int32
fn rename(product: Product, name: Text) -> Product:
return Product(product.id, name, product.price)
The bootstrap tests exercise construction, nested forward-declared fields, field reads and updates, function transfer, imported shape declarations, and negative arity/type/field/recursive-layout checks. Tagged variants are implemented as a separate prototype slice; derives, serialization, and source-level ownership are not implemented. The C runtime arena still governs allocated pointer payloads and does not make shapes memory-safe by construction.
Implemented numeric prototype slice
Int32 is an explicit signed 32-bit integer. int32_from_number(Number) checks the inclusive INT32_MIN..INT32_MAX range; number_from_int32(Int32) widens exactly, and int32_text(Int32) formats a decimal value. Addition, subtraction, multiplication, division, remainder, and negation check overflow and invalid division at runtime. Option[Int32] and Result[Int32, E] use the same tagged-value checks as other built-in payloads. There is no implicit conversion between Number and Int32.
The current native compiler has a partial Float64 implementation. Source literals accept decimal digits with an optional fractional part and exponent (for example, 12.5, 1.25e2, 5E-3, and 2.5e+1). Exponents require at least one digit; a decimal point requires digits on both sides. Values use C double/IEEE-754 binary64. Parsing rejects malformed, overflowing, and non-finite values. Arithmetic rejects division by zero and non-finite results; % is not defined for Float64.
Conversions are explicit and exact-only. float64_from_number(Number) rejects integers outside ±2^53, the range in which every integer is exactly representable in binary64. number_from_float64(Float64) rejects fractional, out-of-range, and non-round-tripping values. float64_text(Float64) formats with 17 significant digits. Float64 remains unsuitable for money; Decimal and Money are still unimplemented.
Security considerations
Non-nullability and typed errors reduce classes of mistakes but do not prove memory safety. That claim depends on RFC 0002 and on validating the compiler's generated-code boundary. Untrusted data must enter as untrusted text/bytes and pass explicit parsing/validation before it becomes a domain type.
Staged implementation
- Specify syntax, type checking, and negative compiler tests for optional/error exhaustiveness.
- Partial: Implement product
shapevalues and the bounded taggedvariantprototype with checked constructors and exhaustive block matches. Cyclic by-value shape layouts, derives, serialization, and language-level ownership remain. - Implement generics and trait constraints after monomorphization or dictionary-passing strategy is selected.
- Migrate API metadata to these types only after compatibility diagnostics can explain the migration.
Acceptance criteria
- The language specification defines type inference, visibility, matching, and error propagation without relying on examples alone.
- Positive and negative conformance tests cover each rule, including ambiguous
From, missing match arms, and invalid?use. - Generated native code passes ASan/UBSan and representation tests for nested options, results, and recursive types.
- No source compatibility claim is made before the migration plan is documented.
Open questions
- Which numeric types belong in the first standard library release?
- Should generic inference cross module boundaries, and what are the compile-time limits?
- Should default expressions be evaluated at the call site as proposed or once in the defining module?