ADR-333: A Type Declares comptime Value Parameters in Its Brackets, as Array[T, N] Does, and a Function Takes a comptime Parameter Its Call Writes as Any Other

Status: Accepted Date: October 7, 2026 (#121) Answers: whether a program can declare a type that takes a value - a header’s name, a decimal’s scale - and how it is written Related: ADR-332 D3 (Header["X-Api-Key"]); ADR-330 (a type as an argument); ADR-318 (a default is a build-time value); Part I 4.5 (Array[T, N]), 4.6, 5.1

1. Question

Can a type a program declares take a value, as Array[f64, 3] takes its length - and where is the value written?

Array is the one type with a value in its brackets; a program cannot declare another. A header’s name on the wire, a decimal’s precision and scale, a buffer’s capacity are values a type is made with.

2. How others do it

  Values in a type
Rust const generics, declared: struct Buf<const N: usize>
C++ non-type template parameters, declared
Zig any build-time value; a function returns the type
Idris, Lean dependent types

From memory, not checked against a version. What transfers: declared value parameters, the common case in Rust and C++.

3. Options, weighed

Option Why it lost
A function that returns a type while building (Zig) a type nobody sees without running code; a signature that checks nothing; a ledger that must carry code; identity of computed types (ADR-323 §3)
Values in round brackets, Header("X-Api-Key") Type(…) is a constructor call, and since ADR-330 a type may stand where a value does
Only Array a program could not say what std says
Telling a value parameter from a bounded type parameter by what follows : [path: String] and [T: Summarize] read alike; the difference is found only by looking the name up
const before a type’s value parameter (Rust), since in a type’s brackets every argument is known while building and the word there says a value, not a type one word for one thing: a parameter whose value is known while building, wherever it stands
A function’s value parameter in brackets and named again in its parameter list, fn get[comptime path: String](self, path, …) the name twice, where comptime on the parameter says the same

4. Decision

D1 — A value parameter is declared in the brackets with comptime and its type.

pub struct Decimal[comptime precision: i64, comptime scale: i64; comptime rounding: Rounding = Rounding::HalfEven] { … }
pub struct Header[comptime name: String; T = String] { … }

A bracket holds type parameters, T or T: Bound, and value parameters, comptime name: Type; the word tells them apart where they are written, not by what the name after : turns out to be. Those after ; are named at the use and have a default, as a function’s options do (Part I 5.1); a type parameter there has a type as its default.

D2 — An argument is a build-time value, and two uses are one type when their arguments are equal.

Decimal[10, 2] and Decimal[10, 2] are one type, Decimal[10, 3] another. An argument that is not a build-time value is refused as a comptime’s is. Inside the declaration a value parameter is a constant.

D3 — A function’s parameter may be comptime: its argument is written as any other, and is a build-time value.

pub fn get[F: Fn](self, comptime path: String, handler: F) -> Server {
    comptime ROUTE = http::Route::pattern(path)
    …
}

.get("/orders/{id}") fn(id: i64) { … }

The argument is held to what a comptime binding’s initialiser is held to (Part II 10.2): a literal, a comptime, a call the build may run. Another is refused with NK1127 or NK1152, at the argument. Inside, the parameter is a constant, and the function is emitted once per value, as once per type. A refusal that comes of the value - a grammar that rejects it - is reported at the argument too. A caller that passes its own parameter on declares it comptime as well.

Changes ADR-330 §3: comptime on a parameter, refused there because the word marked bindings only.

D4 — A function’s comptime parameter in its brackets is read off the types of its arguments.

fn reversed[comptime N: i64](xs: Array[i64, N]) -> Array[i64, N] { … }
fn dot[comptime N: i64](a: Array[f64, N], b: Array[f64, N]) -> f64 { … }

The call writes nothing for it: N is the length the argument’s type has, as T is read off an argument (Part I 4.6). Arguments that give it two values are refused. Inside, it is a constant, and a result type may name it. A comptime parameter in the parameter list is written by the call (D3); one in the brackets is read off a type. Array[T] as a parameter is a comptime length nobody names.

D5 — Array[T, N] is such a type.

It needs no change: it was the first.

5. Consequences

6. What this does not decide

This site is open source. Improve this page.