Types and Data
How data is modeled in the current bootstrap subset and what is still intentionally missing.
Builtin scalar values
Today the core scalar values are:
intstringbooljson
There is also unit, which represents "no meaningful value", and runtime-level
values such as tasks, channels, and cancellation tokens that appear through
specific language features.
When you need explicit type contracts on built-in containers and async values, the current bootstrap surface supports parameterized annotations such as:
list[int]dict[int]channel[int]task[int]Result[int, RuntimeError]
Lists
Lists are the first built-in sequential container.
values: list[int] = [10, 20, 30]
return values[0] + len(values)Current list rules:
- lists are currently homogeneous once the type layer can determine element type
- indexing requires an integer index
- negative indexing is not supported
- out-of-bounds access is rejected at runtime in the bootstrap evaluator
append(list, value)returns a new listfirst(list)andlast(list)returnResult[element, RuntimeError]slice(list, start, end)returnsResult[list[element], RuntimeError]reverse(list)returns a new reversed listsort(list)returns a new deterministically sorted list forlist[int]andlist[string]min(list)andmax(list)returnResult[element, RuntimeError]forlist[int]andlist[string]
List helpers are explicit value construction, not invisible mutation.
Dicts
The current dict baseline is explicit, immutable-friendly, and now has literal syntax for the common case:
store: dict[int] = {"ok": 7, "warn": 2}
return store["ok"] + len(store)When you need to build a new dict step by step, insert(...) is still the
honest tool:
base: dict[int] = {"ok": 7}
next = insert(base, "warn", 2)
return next["warn"]Current dict rules:
- keys currently must resolve to
string - literal values currently must stay type-compatible
dict()creates an empty dict when you want to start from nothinginsert(dict, key, value)returns a new dictkeys(dict)returnslist[string]in deterministic key ordervalues(dict)returns values in the same deterministic key order- indexing requires an existing key
contains(dict, "key")checks key membershiplen(dict)returns entry count
Numeric expressions
The bootstrap numeric surface now supports:
value = -(8 + 2) / 5
rest = value % 2Current rules:
- unary
-requires anint /and%are integer-only- division and modulo by zero are rejected at runtime
Comparisons
Comparison is now split into two explicit contracts:
- equality through
==and!= - ordering through
<,<=,>, and>=
Current rules:
- equality works for
int,string,bool,json,unit,list[T],dict[T], same-typestruct, same-typeenum, and compatibleResult[T, E]values when their nested members are also equality-comparable - ordering works only for
intandstring - string ordering is lexicographic
- channels, tasks, and cancellation tokens are not comparable, even when nested inside larger values
Structs
Structs are the current user-defined aggregate type.
struct Point:
x: int
y: int
fn total(point: Point) -> int:
return point.x + point.yCurrent struct rules:
- fields are declared in the type
- constructor calls use declaration order
- field access requires a real struct value
- unknown fields are rejected
Structs are how you model data that has named parts and stable shape.
Enums
Enums are the current way to model finite state, and they now support both unit variants and payload variants.
enum JobState:
Ready
Running(pid: int)
Failed(message: string)Current enum rules:
- variant names must be unique inside the enum
- payload fields are named and typed in the declaration
- unit variants are referenced as
EnumName.Variant - payload variants are constructed as
EnumName.Variant(value, ...) - same-type enums participate in structural equality when their payload fields are comparable
Use enum when the question is "which state am I in?" rather than "which fields do I have?"
Result values
Result[T, E] is the current recoverable-error baseline.
fn halve(value: int) -> Result[int, string]:
if value % 2 != 0:
return Result.Err("odd")
return Result.Ok(value / 2)Current rules:
Result[T, E]is a built-in parameterized typeResult.Ok(value)constructs a success payloadResult.Err(error)constructs an error payload- result values can be handled with exhaustive
match - result values participate in equality when both payload sides are equality-comparable
- postfix
expr?unwrapsOk(value)and returns early onErr(error) - the enclosing function must return a compatible
Result[_, E]
Operational helpers now use the same contract:
env("NAME")returnsResult[string, RuntimeError]parse_int(text)returnsResult[int, RuntimeError]first(values)returnsResult[T, RuntimeError]slice(values, start, end)returnsResult[list[T], RuntimeError]min(values)returnsResult[T, RuntimeError]max(values)returnsResult[T, RuntimeError]read_file(path)returnsResult[string, RuntimeError]recv(channel)returnsResult[T, RuntimeError]- JSON, CSV, TOML, and HTTP helpers also return
Result
RuntimeError
RuntimeError is the built-in operational error enum.
It currently includes:
EnvMissing(name: string)Io(message: string)ChannelClosedCancelledTaskFailed(message: string)TaskPanicked(task: string)ParseInt(message: string)EmptySequence(message: string)Slice(message: string)Json(message: string)Csv(message: string)Toml(message: string)HttpRequest(message: string)HttpStatus(code: int, body: string)
Methods
Methods are explicit, not magical.
fn Point.with_bonus(self: Point, extra: int) -> int:
return self.x + self.y + extraImportant rule:
- the receiver type is declared in the method name
- the first parameter must match that receiver type
How to think about data in gof
A useful rule of thumb:
- use scalars for direct values
- use lists for ordered homogeneous data
- use dicts for string-keyed lookup tables
- use structs for named shaped data
- use enums for closed state sets
- use
Resultfor explicit recoverable success/error flow