Reference

Things

Generated from LANGUAGE.md at bf2cba0, 2026-08-22.

Vox's eleven builtin types are the compiler's own composite values — a buffer is [capacity][length][flags][data] with 's reading a field at a fixed offset. A thing opens that same mechanism to the program: a user-defined composite value type, built from named fields, with every offset fixed at compile time. No vtables, no dispatch, no runtime component — a thing is a layout, copied, printed, and compared by the compiler the way a buffer is.

A thing is defined once, at the top level, and its name then works everywhere a builtin type keyword works: in declarations, parameters, and return types. A definition declares a type — it allocates nothing and emits no code, so the only output around it comes from the ordinary statements.

examples/delivery.vox is a complete program built from two things of its own: it declares them, makes one with a manifest member, nests one inside the other, copies, prints, and compares them.

Defining a thing

A thing called point has
  a number called x is 0,
  a number called y is 0.

Print "defined".

The keyword is thing, and the verb is has. A definition has two kinds of entry:

  • a data fielda <type> called <name>, with an optional is <literal> default;
  • a function membera function called <name>, the manifest (see The manifest below).

A field without a default takes its type's zero value. thing is a keyword only inside this construct — everywhere else it is an ordinary identifier, exactly like send, so a variable may be called thing:

A thing called point has
  a number called x is 0,
  a number called y is 0.

a number called thing is 42.
Print thing.

A thing name may be a bare word (point) or a quoted multi-word name ('bounding box'), the same forms any identifier takes:

A thing called 'bounding box' has
  a number called width is 1,
  a number called height is 1.

a 'bounding box' called viewport.
Print viewport's width.

Field types in v1. A field may be number, float, boolean, time, or any previously defined thing (things nest to any depth — see Nesting). text, list, map, and buffer fields are deferred: they carry references and would reopen the aliasing question value copy semantics (below) is designed to avoid.

The article rule

a/an pairs with types and with values coming into being; the pairs with known identifiers. The rule is load-bearing in the surface syntax, so it is worth naming once:

  • A thing called point has ... — a type comes into being, so A.
  • a point called origin. — a value of that type comes into being, so a.
  • the point's 'placed at' in a member definition (To do the point's 'placed at') — point is a known identifier (the type, declared in the manifest), so the.
  • a point's 'placed at' with 1 and 0 — a new point comes into being from the maker, so a.

The same word, two articles, two meanings: the point's reads a known member; a point's calls a maker that brings a new point into being.

Declarations and field access

A thing name is a type noun everywhere the builtin ones are, so every declaration form works. All three lines below declare a point and give every field its declared default:

A thing called point has
  a number called x is 0,
  a number called y is 0.

a point called origin.
Set origin's x to 3.
Set origin's y to 4.
Print origin's x.
origin's y is origin's y add 1.
increment origin's x.
Print "origin sits at {origin's x}, {origin's y}".
If origin's x is greater than 3 then,
    Print "the origin moved right".

A field is an ordinary expression and an ordinary lvalue everywhere either is allowed — read, Set ... to, bare assignment, increment, decrement, format-string interpolation, and a comparison in a condition all appear above. Create declares with defaults too, and a quoted variable name is read and written through the same possessive:

A thing called point has
  a number called x is 0,
  a number called y is 0.

Create a point called 'the far corner'.
Print 'the far corner''s x.
Set 'the far corner''s y to 2.
Print 'the far corner''s y.

A field with no default takes its type's zero. A float and a boolean field carry defaults; a number field with none is 0:

A thing called 'water tank' has
  a float called 'depth in metres' is 1.5,
  a boolean called 'the pump is running' is true,
  a number called 'litres drained'.

a 'water tank' called cistern.
Print cistern's 'depth in metres'.
If cistern's 'the pump is running' then,
    Print "the pump is running".
Print cistern's 'litres drained'.

A thing declared inside a function is local to that function (its storage is the stack, not .bss):

A thing called point has
  a number called x is 0,
  a number called y is 0.

To 'plot a point'.
  a point called cursor.
  Set cursor's x to 9.
  increment cursor's y.
  Print "the cursor sits at {cursor's x}, {cursor's y}".

'plot a point'.

Nesting

A field may be a thing, so things nest to any depth. A nested thing contributes its own bytes inline, so a chained possessive is one sum of compile-time offsets — never a pointer chase — and the route's own 'route number' sits after the whole nested segment:

A thing called point has
  a number called x is 0,
  a number called y is 0.

A thing called segment has
  a point called start,
  a point called end.

A thing called route has
  a segment called leg,
  a number called 'route number'.

a route called commute.
Set commute's leg's start's x to 3.
Print commute's leg's start's x.
increment commute's leg's end's y.
Set commute's 'route number' to 66.
Print commute's 'route number'.

Defaults apply recursively: a field whose type is a thing takes that thing's own defaults, written into the nested bytes at declaration, so a stamp carried by a letter begins life with its own defaults even though nothing initialises it:

A thing called stamp has
  a number called 'day sent' is 25,
  a number called 'cost in pence' is 12.

A thing called letter has
  a stamp called posted,
  a number called 'weight in grams' is 2.

a letter called invitation.
Print invitation's posted's 'day sent'.
Print invitation's posted's 'cost in pence'.
Print invitation's 'weight in grams'.

A thing containing itself — directly, or through other things — has no finite size, so the definition that closes the cycle is a compile error naming the chain:

A thing called ouroboros has
  an ouroboros called tail.
(compile error: ouroboros contains ouroboros
   no finite size)

Things are acyclic by two mechanisms. Within one file, the defined-earlier ordering rule makes a cycle unconstructible: a field type must be a thing defined above the line, so a thing can never name itself or a thing defined below it. Across files reached by see, the analyzer's registry DFS proves the merged registry is acyclic. The DFS is load-bearing: it is what keeps the merged, multi-file registry acyclic, and it stands as defence-in-depth alongside the within-file ordering rule.

Value copy semantics

A thing is a value. Assignment copies the whole thing, and the copy shares nothing with the original — a thing's size is a compile-time constant, so a copy is a run of inline moves, no allocation and no pointer left aliased:

A thing called point has
  a number called x is 0,
  a number called y is 0.

a point called origin.
Set origin's x to 5.
a point called moved is origin.
Set moved's x to 9.
Print origin's x.
Print moved's x.

The three spellings of assignment — a declaration with an initialiser, a bare is, and Set ... to — are all assignment, so all three copy. A copy is deep by construction: a nested thing is just more bytes, so copying a letter carries its point along and neither half is shared:

A thing called point has
  a number called x is 0,
  a number called y is 0.

A thing called letter has
  a point called postbox,
  a number called 'weight in grams' is 2.

a letter called invitation.
Set invitation's postbox's x to 3.
a letter called reply is invitation.
Set reply's postbox's x to 7.
Print invitation's postbox's x.
Print reply's postbox's x.

The same is true across a call. A function receives a copy of a thing and hands one back by returning it; nudging the parameter cannot reach the caller's point, because the only way out is the Return, which copies into the caller's own storage:

A thing called point has
  a number called x is 0,
  a number called y is 0.

To nudged with a point called start.
  Set start's x to start's x add 1.
  Return a point, start.

a point called before.
The after is nudged of before.
Print before's x.
Print after's x.

The after is nudged of before. declares after from what the call returns, so a maker never has to have its type written twice. A whole nested thing read out of a segment is copied the same way:

A thing called point has
  a number called x is 0,
  a number called y is 0.

A thing called segment has
  a point called start,
  a point called end.

To nudged with a point called start.
  Set start's x to start's x add 1.
  Return a point, start.

a segment called span.
Set span's start's x to 40.
a point called 'the far end' is nudged of span's start.
Print 'the far end''s x.
Print span's start's x.

Because a thing is a whole shape, not a value, the things you cannot do to one are named rather than done to its first field. Assigning a single value to a whole thing, or stepping one with increment, is rejected with the field to write named instead:

A thing called point has
  a number called x is 0,
  a number called y is 0.

a point called origin.
Set origin to 5.
(compile error: 'origin' holds a whole point, so only a whole point can be copied into it
   A copy source is a variable holding a point, a field that holds one, or a call that returns one (plan 310 §5)
   To write one field instead, name it - point's fields are: x, y)
A thing called point has
  a number called x is 0,
  a number called y is 0.

a point called origin.
increment origin.
(compile error: 'origin' holds a whole point, not a value
   A whole thing is copied, passed, and returned whole (plan 310 §5)
   its fields are: x, y)

Writing one field is what those lines mean: Set origin's x to 5. A thing also cannot be interpolated into a text initializer (a text initializer is a different sink from Print) or compared with a single value; see Printing and Equality.

Printing a call's result directly needs a variable, because the result is a whole thing that must land in storage before it can be read:

A thing called point has
  a number called x is 0,
  a number called y is 0.

To nudged with a point called start.
  Set start's x to start's x add 1.
  Return a point, start.

a point called before.
Print nudged of before.
(compile error: A call to 'nudged' returns a whole point, which is not a value
   What a call returns is copied into a point)

The workaround is the inference form above — declare a scratch slot from the call, then print it:

A thing called point has
  a number called x is 0,
  a number called y is 0.

To nudged with a point called start.
  Set start's x to start's x add 1.
  Return a point, start.

a point called before.
The after is nudged of before.
Print after.

Printing

Print p. walks the fields in definition order and recurses into the things they hold, map-style. Every field name is baked into the emitted program, so nothing is read from a descriptor and nothing is allocated. A quoted field name prints in the quotes it is written with, and a function member takes no part — it is the type's API, not its state:

A thing called point has
  a function called 'placed at',
  a number called x is 0,
  a number called y is 0.

A thing called segment has
  a point called start,
  a point called end.

A thing called stamp has
  a number called 'day sent' is 25,
  a float called 'cost in pounds' is 1.5,
  a boolean called 'first class' is true.

To do the point's 'placed at', with a number called x and a number called y.
  a point called plotted.
  Set plotted's x to x.
  Set plotted's y to y.
  Return a point, plotted.

a point called origin.
Set origin's x to 5.
Print origin.

a segment called span.
Set span's end's y to 2.
Print span.
Print "the span runs {span}".

a stamp called posted.
Print posted.

The corner is a point's 'placed at' with 3 and 4.
Print corner.

A whole thing interpolates into a format string under Print (Print "the span runs {span}".), because Print is the sink that renders the fields. A text initializer is a different sink — it builds its bytes in a buffer — and interpolating a whole thing there is rejected, naming the field to interpolate instead:

A thing called point has
  a number called x is 0,
  a number called y is 0.

a point called origin.
a text called note is "the point is {origin}".
(compile error: 'origin' holds a whole point, which only `Print` can interpolate
   Interpolate a field instead - point's fields are: x, y)

Equality

is between two values of the same thing compares those same fields at the same depth; is not is its negation. Like printing, the comparison is written out by the compiler, so it recurses into nested things:

A thing called point has
  a function called 'placed at',
  a number called x is 0,
  a number called y is 0.

To do the point's 'placed at', with a number called x and a number called y.
  a point called plotted.
  Set plotted's x to x.
  Set plotted's y to y.
  Return a point, plotted.

a point called origin.
Set origin's x to 5.
a point called marker.
Set marker's x to 5.
If origin is marker then,
    Print "the marker is where the origin is".
Set marker's y to 9.
If origin is not marker then,
    Print "the marker has moved off the origin".

Two things of different types cannot be compared (origin is span is rejected — only two of the same thing have the same fields), and there is no ordering on a whole thing — origin is greater than marker is rejected, naming the field to compare instead:

A thing called point has
  a number called x is 0,
  a number called y is 0.

a point called origin.
a point called marker.
If origin is greater than marker then,
    Print "further along".
(compile error: 'origin' holds a whole point, which nothing puts in order
   Two things are compared for equality only (plan 310 §8)
   Compare a field instead - point's fields are: x, y)

The manifest

A thing's callable API is declared in one place, the manifest: each a function called <name> entry names a member. The member is then defined with To do the <type>'s <name>, and do is a keyword only in that position — everywhere else it is an ordinary identifier. The member definition uses the point's (a known identifier) — the article rule:

A thing called point has
  a function called 'placed at',
  a function called 'reflected through the origin',
  a number called x is 0,
  a number called y is 0.

To do the point's 'placed at', with a number called x and a number called y.
  a point called plotted.
  Set plotted's x to x.
  Set plotted's y to y.
  Return a point, plotted.

To do the point's 'reflected through the origin', with a point called original.
  a point called reflection.
  Set reflection's x to 0 subtract original's x.
  Set reflection's y to 0 subtract original's y.
  Return a point, reflection.

Function members take no storage, so layout, copy, printing, and equality see only the data fields.

Every declared member returns its owner. That is what gives the manifest a crisp meaning — it lists the functions that produce or transform the thing. A definition whose Return is not Return a point, is a compile error naming both lines:

A thing called point has
  a function called 'placed at',
  a number called x is 0.

A thing called 'grid square' has
  a number called column is 0.

To do the point's 'placed at', with a number called x.
  a 'grid square' called square.
  Set square's column to x.
  Return a 'grid square', square.
(compile error: A declared member returns its own thing: point's 'placed at' must return a point
   hands back a grid square
   A function that computes something else from a point is an ordinary function)

A function computing some other type from a point (like 'magnitude squared') is an ordinary global function, reached by the instance possessive, with no manifest entry at all. The owner-return check reads the body's Return lines, not the signature — so a member whose only Return sits inside an If is not wrongly rejected.

The manifest is checked both ways. A To do naming a member the manifest does not list errors at the definition, naming the entry to add:

A thing called point has
  a function called 'placed at',
  a number called x is 0,
  a number called y is 0.

To do the point's sparkle, with a point called original.
  Return a point, original.
(compile error: point does not declare sparkle - add `a function called sparkle` to the type
   Membership is declared in the thing's definition
   point declares: placed at)

and a declared member nothing defines errors at the type, where the promise was made:

A thing called point has
  a function called 'never written',
  a number called x is 0.

a point called origin.
Print origin's x.
(compile error: point declares 'never written' but nothing defines it
   To do the point's 'never written', with <parameters>.
   Return a point, <value>.)

A member is defined once: a second To do the point's 'placed at' errors at the second definition, naming the first.

The three call forms

Three ways to call, one rule each.

Free call — the function's own name, unchanged, in the global namespace. of, to, with, and on all introduce arguments:

A thing called point has
  a number called x is 0,
  a number called y is 0.

To 'magnitude squared' with a point called corner.
  a number called 'x squared' is corner's x multiply corner's x.
  a number called 'y squared' is corner's y multiply corner's y.
  Return a number, 'x squared' add 'y squared'.

a point called origin.
Set origin's x to 3.
Set origin's y to 4.
Print 'magnitude squared' of origin.

Instance possessivereceiver's 'member': sugar for 'member' of receiver. The receiver fills the function's first parameter; any further arguments follow the call preposition. A field always wins over a function of the same name, because the collision rule refuses that program rather than letting one shadow the other. A receiver is anything that names a whole thing, so a field holding one reads the same way:

A thing called point has
  a number called x is 0,
  a number called y is 0.

A thing called segment has
  a point called start,
  a point called end.

To 'magnitude squared' with a point called corner.
  a number called 'x squared' is corner's x multiply corner's x.
  a number called 'y squared' is corner's y multiply corner's y.
  Return a number, 'x squared' add 'y squared'.

To 'scaled by' with a point called corner and a number called factor.
  a point called scaled.
  Set scaled's x to corner's x multiply factor.
  Set scaled's y to corner's y multiply factor.
  Return a point, scaled.

a point called origin.
Set origin's x to 3.
Set origin's y to 4.
Print origin's 'magnitude squared'.
The 'tripled corner' is origin's 'scaled by' on 3.
Print 'tripled corner''s x.

a segment called 'the line'.
Set 'the line''s end's y to 12.
Print 'the line''s end's 'magnitude squared'.

Type possessivea <type>'s 'member': calls a member declared in the manifest. The article is a because a new thing comes into being. This is the only way to call a maker — a member whose first parameter is not the thing:

A thing called point has
  a function called 'placed at',
  a number called x is 0,
  a number called y is 0.

To do the point's 'placed at', with a number called x and a number called y.
  a point called plotted.
  Set plotted's x to x.
  Set plotted's y to y.
  Return a point, plotted.

The pin is a point's 'placed at' with 1 and 0.
Print pin's x.

A maker cannot be reached by the instance possessive (a receiver has nothing to fill), and the message says so rather than reporting the member as missing — the manifest does declare it:

A thing called point has
  a function called 'placed at',
  a number called x is 0.

To do the point's 'placed at', with a number called x.
  a point called plotted.
  Set plotted's x to x.
  Return a point, plotted.

a point called origin.
Print origin's 'placed at'.
(compile error: point declares 'placed at', but a receiver cannot reach it here
   'placed at' is a maker: its first parameter is not a point, so a receiver has nothing to fill
   Name the type instead: `a point's 'placed at' with <arguments>`)

A member whose first parameter is the thing gets both the type possessive and the instance possessive:

A thing called point has
  a function called 'placed at',
  a function called 'reflected through the origin',
  a number called x is 0,
  a number called y is 0.

To do the point's 'placed at', with a number called x and a number called y.
  a point called plotted.
  Set plotted's x to x.
  Set plotted's y to y.
  Return a point, plotted.

To do the point's 'reflected through the origin', with a point called original.
  a point called reflection.
  Set reflection's x to 0 subtract original's x.
  Set reflection's y to 0 subtract original's y.
  Return a point, reflection.

The pin is a point's 'placed at' with 1 and 0.
The opposite is pin's 'reflected through the origin'.
The 'opposite of the opposite' is a point's 'reflected through the origin' of opposite.
Print opposite's x.
Print 'opposite of the opposite''s x.

A member name belongs to its owner, not to the program: two things may each declare a 'placed at', and the two definitions compile under distinct internal names, so the same member name is fine on two different types:

A thing called point has
  a function called 'placed at',
  a number called x is 0,
  a number called y is 0.

A thing called 'grid square' has
  a function called 'placed at',
  a number called column is 0,
  a number called row is 0.

To do the point's 'placed at', with a number called x and a number called y.
  a point called plotted.
  Set plotted's x to x.
  Set plotted's y to y.
  Return a point, plotted.

To do the 'grid square''s 'placed at', with a number called column and a number called row.
  a 'grid square' called square.
  Set square's column to column.
  Set square's row to row.
  Return a 'grid square', square.

The 'marked square' is a 'grid square''s 'placed at' with 5 and 6.
Print 'marked square''s column.
Print 'marked square''s row.

do stays an ordinary identifier outside To do the <type>'s: To do. defines a function called do, and do. calls it.

One identifier space

Type names, variable names, and function names share a single global identifier namespace. This is what makes the point's unambiguous: there is only one point. Reusing a name is first-come-first-served, and the second definition errors at its own line, naming the first — whatever kind the first was:

a number called point is 0.
A thing called point has
  a number called x is 0.
(compile error: 'point' is already defined as a variable on line 1
   identifier space)

The same error names a function, a parameter, a loop variable, an inferred variable, or another thing, whichever came first. A thing's own fields and members live in a separate per-type member space (a type owns one), so point's x and segment's x do not collide. The collision rule there is first-come-first-served too: the second definition of any name in a type's member space — a field, a declared member, or a global function whose first parameter is that type — errors at its own line, pointing at the first:

A thing called point has
  a number called x is 0,
  a number called y is 0.

To x with a point called corner.
  Return a number, corner's y.
(compile error: point already has a field called 'x', so a function taking a point cannot be called 'x' too
   point is defined on line 1. A type owns one member space)

Definitions are top-level only

A thing is defined where a function is defined — at the top level. Its layout is fixed for the whole program and has no block scope, so a definition inside an If, a loop, or a function body is a compile error, and the message says to move it above the block:

To 'take a reading'.
  A thing called measurement has
    a number called degrees is 0.
  Print "taken".
(compile error: A thing is defined at the top level, like a function
   Canonical form: A thing called measurement has <fields>.
   Move the definition above the block it is written in)

The ordering rule is about the definition, not about an instance of it. A definition stands above every use of its name; an instance is an ordinary top-level variable, so it obeys the ordinary rule instead — "variables declared at top level are global and can be used inside functions", wherever on the page the declaration is written:

To 'show it'.
  Print origin's x.      (reads the global declared below)

a point called origin.

Cross-file definitions

A thing defined in one file is usable from another via see (the definition is parsed into the program the way a function is). The whole surface crosses the boundary: the type noun in a declaration, a field read and write, the manifest member reached by the type possessive, and a global function taking the thing reached by the instance possessive. The seen file arrives where the see is written, so the same defined-earlier rule that orders one file orders the pair — every use below stands after the definition it names.

(./include/geometry.vox — the definition and the maker travel together)
A thing called point has
  a function called 'placed at',
  a number called x is 0,
  a number called y is 0.

To do the point's 'placed at', with a number called x and a number called y.
  a point called plotted.
  Set plotted's x to x.
  Set plotted's y to y.
  Return a point, plotted.

(An ordinary global function taking a point first, so the including file
 can reach it through the instance possessive as well as by name.)
To 'shifted east' with a point called start.
  Set start's x to start's x add 1.
  Return a point, start.
(Consumer file — sees the definition above.)
see "./include/geometry.vox".

a point called origin.
Set origin's x to 11.
Print origin's x.

The corner is a point's 'placed at' with 3 and 4.
Print corner's x.
Print corner's y.

The 'shifted corner' is corner's 'shifted east'.
Print 'shifted corner''s x.

A type name is one identifier across the whole compilation: defining the same thing in two files reached by see errors at the second definition, naming the other file. The diagnostic reads 'point' is already defined as a thing on line 4, then names the file that defined it first (include/point_defined_elsewhere.vox) and the rule (identifier space).

A see of a file that cannot be read is an error.

.lib export of a thing is not yet supported

A .lib interface file names types by noun, and no noun spells a user-defined thing, so an exported signature that takes or returns a thing cannot be written. Ordinary compilation is unaffected; an exported library function whose signature mentions a thing is refused with a message naming the field and the canonical workaround — pass the thing's fields across the boundary instead. A library that exports a function To 'nudged east' with a point called start. — taking a point and, in the same case, returning one — is refused when compiled with --shared:

takes a point ('start'), which a library interface cannot describe yet returns a point, which a library interface cannot describe yet A thing is a layout private to one compilation

The same source compiles fine as an ordinary program; the refusal fires only at the library interface, because the interface has no noun for a user-defined thing. The diagnostic names each crossing field and points at the workaround — pass start's x and start's y as separate values.

Sentence consumption and multi-line definitions

A thing definition is a new place the sentence consumption rules bite. Its entries are comma-separated, and the construct closes on a period or a blank line, the same termination rules every other construct follows: a period closes the entry list, and a blank line force-closes it (along with anything else still open). Indenting the entries is conventional but not required — the commas and the terminator carry the structure.

Definition diagnostics

The definition construct creates a family of sentences that are never valid Vox. Each gets a targeted error stating the intent it recognises and naming the canonical form.

Create a thing called point.
(compile error: A thing is defined, not created as a variable
   Canonical form: A thing called point has <fields>.)
A thing called point is 5.
(compile error: 'is' declares a variable; a thing definition uses 'has'
   Canonical form: A thing called point has <fields>.)
A thing called point has.
(compile error: A thing needs at least one field
   Canonical form: A thing called point has)

A definition listing only manifest entries describes a zero-byte thing, so v1 requires at least one data field:

A thing called point has
  a function called 'from polar'.
(compile error: A thing needs at least one field
   `a function called <name>` declares callable API, not storage.)

A field default must be a literal of the field's own type — a computed value belongs in a function that returns the thing:

A thing called point has
  a number called x is 1.5.
(compile error: Field 'x' of thing 'point' is a number, but its default is a float
   literal of the field's own type)

Declaring with an unknown type name keeps the existing unknown-type error, extended to suggest near-miss user-defined type names alongside the builtins:

A thing called point has
  a number called x is 0,
  a number called y is 0.

a poimt called origin.
(compile error: Unknown type 'poimt'
   did you mean
   point)

Type predicates and the runtime tag

User-defined things are not in the runtime tag system in v1. The type nouns is a <type> recognises are the builtins (number, text, decimal, boolean, list, map); there is no is a point yet, and a list or map of user things, or a value holding one, is likewise deferred. Things live in the compile-time type table, not the runtime tag.