Proposal B — string<..n>
Status: rejected 2026-11-02: the same misreadings as
A, and it takes <> from generics;
Proposal D was accepted · See the
section overview for the question all four answer
The idea: a string's bound follows it in angle brackets: string<1..32>.
Array bounds use the array suffix: T[1..10].
Shape
const MAX_NAME: u32 = 64
/// A display name: 1 to 32 characters.
type AccountName = string<1..32>
/// Raw image bytes, at most 64 KiB.
type Avatar = u8[..65536]
struct Customer {
id: u64
name: AccountName
optional email: string<..254>
/// Up to 10 tags of up to 32 characters each.
tags: string<..32>[..10]
optional avatar: Avatar
}
/// Exactly 3 floats: the fixed array, unchanged.
struct Point {
pos: f32[3]
}
struct Order {
customer: Customer
lines: OrderLine[1..100]
discount: union(string<4..16> u32)
optional note: string<..MAX_NAME>
}
struct OrderLine {
sku: string<8..8>
quantity: u32
}
/// Up to 16 rows, each with 2 to 8 labels of up to 20 characters.
struct Board {
rows: string<..20>[2..8][..16]
}
error OutOfStock {
message = f"{item} is out of stock"
item: string<..64>
}
struct Signup {
@validators = [StringBounds(min_chars = 3)]
handle: string<..32>
}
protocol Orders {
function place(order: Order) -> u64;
function search(query: string<1..100>, limit: u32) -> Order[..50];
function rename(id: u64, name: AccountName) -> ();
}
Each suffix applies to what's on its left: string<..20>[2..8][..16] is up to
16 rows, each of 2 to 8 strings, each of up to 20 characters.
The case for it
- Reads as a type inside unions and parameter lists:
union(string<4..16> u32),search(query: string<1..100>, …). - Familiar from Rust and TypeScript, where
<…>qualifies a type. - No overlap with
(), the unit type. - Small grammar change: an optional range after
string.<and>are unused in the grammar today.
The case against
- Looks like
string[], as Proposal A does: a bracket right afterstring, next to the array suffix. It needs the same explanation in the docs, hover and diagnostics. - Uses up
<>. If generics come (Page<User>),string<..64>andPage<User>share brackets with different meanings. - Range bounds on scalars (
u8<1..12>), if ever allowed, would look the same as a size bound but mean something else (Which types take a bound).
When this would be the right call
If generics are not expected, and readability inside unions and parameter lists matters most.