Language reference
Values & described things
Vision has a small set of scalar values, three in-memory collection types, and named shapes for program-specific data. This page gives the current surface, limits, and ownership rules for each.
Every rule below describes the current Vision compiler. For a guided introduction, start with Values, text, and lists or Kinds, enums, and unions.
Scalar values
Text
A double-quoted literal is text. Interpolation places an expression inside braces, as in "count: {count}". Text is UTF-8; length and slicing count Unicode code points, not bytes or grapheme clusters.
Text comparisons inspect content. Ordering comparisons are not defined. Text joins with then, not plus. A text value stored in a field or container is copied into storage owned by that destination.
Whole numbers, decimals, and bytes
An integer literal is a whole; a literal with a decimal point is a decimal. The default C lowering is signed 64-bit int64_t for whole values and 64-bit double for decimals. --float changes decimal lowering to 32-bit float; --double selects the default explicitly.
A byte is an unsigned 8-bit value from 0 through 255. Vision has no byte-literal spelling: integer literals begin as whole values, and a byte-typed operation supplies the narrower context. A list of bytes accepts an in-range whole value and rejects a value outside 0β255. Reading that list produces a byte, as the fixture below uses for sample.octet.
There are no other exact-width integer types: no signed 8-, 16-, or 32-bit integers and no unsigned 16-, 32-, or 64-bit integers. Use whole when signed 64-bit storage is suitable, byte for 0β255, or a sealed by hand: host boundary when an ABI requires another width. Whole-number addition, subtraction, multiplication, and negation trap on overflow rather than wrapping. Whole literals outside the signed 64-bit range and decimal literals outside the default double range are compile errors.
Current --float limitation: literal checking still uses the wider double range. A literal that is finite as a double but too large for a 32-bit float can therefore reach C emission and become infinity. Keep float-mode literals within the 32-bit range; the compiler does not enforce that narrower boundary yet.
Yes or no
yes and no have type yes or no, also named bool in typed signatures. A boolean may be read directly as a condition, such as when sample.ready:. Vision does not treat numbers, collections, or described things as truthy. A yes/no value is not accepted in text interpolation; branch on it or convert the result to text explicitly.
kind measurement:
label: text
count: whole
ratio: decimal
octet: byte
ready: yes or no
note: text or nothing
to begin:
the bytes are a list of bytes
add 255 to the bytes
the sample is a measurement:
label is "north"
count is 42
ratio is 2.5
octet is the first value in the bytes
ready is yes
note is nothing
say "{sample.label}: {sample.count}, {sample.ratio}, byte {sample.octet}"
when sample.ready:
say "ready: yes"
when sample.note is nothing:
say "note: nothing"kind measurement:
label: text
count: whole
ratio: decimal
octet: byte
ready: yes or no
note: text or nothing
to begin:
the bytes are a list of bytes
add 255 to the bytes
the sample is a measurement:
label is "north"
count is 42
ratio is 2.5
octet is the first value in the bytes
ready is yes
note is nothing
say "{sample.label}: {sample.count}, {sample.ratio}, byte {sample.octet}"
when sample.ready:
say "ready: yes"
when sample.note is nothing:
say "note: nothing"Nothing and something
nothing is the absent value. A field becomes optional by appending or nothing to its declared type, for example note: text or nothing. Some built-ins also return a value that may be absent. Vision does not turn every value into an implicit optional type.
Test a nullable value with is something or is nothing. A read is valid only where control flow proves the value is present. Assignment invalidates an earlier proof, and a proof survives a branch join only when every incoming path establishes it. Testing an ordinary whole, decimal, byte, set, callable, or always-present aggregate for presence is a compile error; these types have no null or truthiness meaning.
A required construction field rejects nothing. An optional field accepts it and records absence. The scalar fixture constructs an absent optional field and inspects it without reading a nonexistent payload.
Lists, maps, and sets
Lists
Declare an empty typed list with the scores are a list of wholes. The type after of is a bare, one-word type name; use whole, wholes, number, or numbers, not whole number, in this position. Text, decimal, byte, and a declared kind are also valid element types. A comma-separated naming such as the scores are 8, 13 creates a list and infers its element type.
add value to list appends. Positions are one-based. Reads use the value at position in list, the first value in list, or the last value in list. The expression forms end the program on a missing position. Use the returning statement the value at position in list returning name with a | missing: clause when the caller must handle that case. Lists retain duplicates and preserve order.
Maps
the glossary is a map from text to text declares a map. Keys may be text, whole, or byte. Values may be text, whole, decimal, byte, yes/no, or a described thing. put value for key in map inserts or replaces without failing. The map owns copies of its keys and values and retains insertion order.
Read with the value for key in map returning name. A miss is a missing failure, not nothing, so the call needs a | missing: clause. map has key is the non-failing presence test. remove key from map silently does nothing when the key is absent.
Sets
A set holds unique text, whole, or byte values. Declare it with the colors are a set of text, or populate it with the colors are a set of text containing "red", "blue". Both is a set and are a set are accepted.
add value to set inserts; a duplicate is a no-op. Use set has value, how many are in set, and each value in set: to inspect it. Iteration follows first insertion order. Decimal elements are rejected because the current set key model supports only text, whole, and byte. Map-only put and get operations are rejected on sets.
to begin:
the scores are a list of wholes
add 8 to the scores
add 13 to the scores
the glossary is a map from text to text
put "a small boat" for "skiff" in the glossary
the value for "skiff" in the glossary returning the meaning
| missing: say "missing" and stop
the colors are a set of text containing "red", "blue", "red"
say "first score: {the first value in the scores}"
say "skiff: {the meaning}"
say "unique colors: {how many are in the colors}"to begin:
the scores are a list of wholes
add 8 to the scores
add 13 to the scores
the glossary is a map from text to text
put "a small boat" for "skiff" in the glossary
the value for "skiff" in the glossary returning the meaning
| missing: say "missing" and stop
the colors are a set of text containing "red", "blue", "red"
say "first score: {the first value in the scores}"
say "skiff: {the meaning}"
say "unique colors: {how many are in the colors}"Kinds and fields
kind release: declares a described thing. Its indented lines declare fields. A bare field, such as title, has its type inferred from use. A typed field uses title: text, title is text, or a title is text. Typed fields may name primitive values, another kind, an enum, a tagged union, a list, or a secret where those types support field storage.
Read a field with item.title or the title of the item. Write a stored field with item.title is "Reference". Dot syntax requires a single-word base local; use a single-word local name when a field must be written. An unknown field or a value of the wrong type is a compile error. A direct by-value cycle between kinds is also rejected.
kind name is immutable: creates a write-once shape: fields may be filled during construction but not changed afterward. Generic kinds use which works on; see Generics and monomorphization for that separate construction rule.
Construction
A bare constructor has the form the item is a release and zero-initializes the shape. It does not require every declared field to be named at that site. Use block construction when the site should be checked for complete required data: the item is a release:, followed by indented field is expression lines.
A construction block accepts field assignments onlyβno loops, effects, failure clauses, or memory block. Every required field must appear once. Unknown fields, duplicates, missing required fields, and type mismatches are compile errors. Nested described things may use another construction block directly under their field.
Name-matched construction uses the point is a coordinates built from (vertical, horizontal). Each item inside the parentheses must be a local name. The compiler matches local and field names, not positions, so source order does not cross-wire fields. Required fields need same-named locals of the right type. Optional fields may be omitted. A bare nothing is not legal in the parentheses because it has no field name; omit the optional name, pass a same-named local that holds nothing, or use field is nothing in a block.
Enums
a phase is one of draft, ready declares one named enum and its closed member set. The declaration stays on one line and has no colon. A member resolves from context when it is assigned, compared, or passed to a field or parameter of that enum type. If the same member word belongs to several enums and no context selects one, the compiler rejects the ambiguous use.
Enum values compare only with members of the same named enum. Ordinary text does not become an enum implicitly. An enum renders as its member name when said or interpolated. It is a scalar value and owns no heap storage.
Tagged unions
A colon after is one of declares a tagged union. Each indented variant starts with a. A bare variant carries no payload; a found, with a detail declares payload fields. with is the only payload connector.
Construct the variant, not the union: the match is a found, then fill its fields. Variant payload field types are inferred from their writes; there is no typed payload declaration syntax in the variant line. A value typed as the union must be narrowed with when value is a found: before a payload field can be read. A tag chain must cover every variant or end in else:.
Current limitation: do not rely on a variant construction block as the only evidence for a heap payload field's type. A text field first seen only inside the match is a found: can survive Vision checking with the wrong inferred C field type and then fail native compilation. The stable form is the plain variant constructor followed by a typed dot write, as in the fixture below. Union values own their active payload, including copied text and nested described things, and release it when replaced or when the scope closes.
Callable types
a presenter is a way to present, given a release called item, returns text declares a callable shape. Every parameter needs an explicit type and binding name. The phrase after a way to is descriptive; the parameter list, optional return type, world-touch marker, mutation marker, and failure tags define the checked shape.
A matching verb name is a value in a callable-typed position. Pass it as in show(item, compact), then invoke the parameter with normal call syntax, such as render(item). The bound verb must match arity, parameter types, return type, world-touch behavior, mutation shape, and the exact failure-tag set. Callables are bare function references: they capture no local state and own no heap storage. A heap value returned through one follows the same caller-owned return rule as a direct verb call.
a phase is one of draft, ready
kind release:
title: text
phase: phase
kind coordinates:
horizontal: whole
vertical: whole
a lookup is one of:
a found, with a detail
a missing
a presenter is a way to present, given a release called item, returns text
to compact(item: release), returns text:
answer "{item.phase}: {item.title}"
to show(item: release, render: presenter):
say render(item)
to describe(outcome: lookup), returns text:
when outcome is a found:
answer "found: {outcome.detail}"
else:
answer "missing"
to begin:
the item is a release:
title is "3.0.2"
phase is ready
show(item, compact)
the horizontal is 7
the vertical is 9
the point is a coordinates built from (vertical, horizontal)
say "point: {point.horizontal},{point.vertical}"
the match is a found
match.detail is "reference"
say describe(match)a phase is one of draft, ready
kind release:
title: text
phase: phase
kind coordinates:
horizontal: whole
vertical: whole
a lookup is one of:
a found, with a detail
a missing
a presenter is a way to present, given a release called item, returns text
to compact(item: release), returns text:
answer "{item.phase}: {item.title}"
to show(item: release, render: presenter):
say render(item)
to describe(outcome: lookup), returns text:
when outcome is a found:
answer "found: {outcome.detail}"
else:
answer "missing"
to begin:
the item is a release:
title is "3.0.2"
phase is ready
show(item, compact)
the horizontal is 7
the vertical is 9
the point is a coordinates built from (vertical, horizontal)
say "point: {point.horizontal},{point.vertical}"
the match is a found
match.detail is "reference"
say describe(match)Secrets
A field declared with password: secret stores tainted text or bytes. Secret data cannot reach say, interpolation, or a diagnostic until the program explicitly reveals it. Arithmetic on a secret is rejected. Equality uses the secret comparison path rather than ordinary text comparison.
reveal the password of the safe returning the shown creates ordinary owned text. The source must be a stored secret place; a computed expression or non-secret value is rejected. Revealing does not erase the source, and the returned text is no longer protected by the secret taint.
wipe overwrites a secret's backing bytes and clears its length. It also accepts a list of bytes or a described thing containing secret fields. It rejects plain text, whole values, and lists of whole values. Reassigning or freeing a secret wipes its old storage before release; copying creates independent owned storage.
kind vault:
password: secret
to begin:
the safe is a vault:
password is "hunter2"
reveal the password of the safe returning the shown
say "revealed: {the shown}"
wipe the password of the safe
reveal the password of the safe returning the cleared
when the cleared is "":
say "wiped"kind vault:
password: secret
to begin:
the safe is a vault:
password is "hunter2"
reveal the password of the safe returning the shown
say "revealed: {the shown}"
wipe the password of the safe
reveal the password of the safe returning the cleared
when the cleared is "":
say "wiped"Ownership and lifetime
Text, lists, maps, sets, described things with heap fields, and tagged unions with heap payloads are managed values. A local owns its storage until it is moved, replaced, or leaves scope. Containers deep-copy owned text and described things on insertion, then free those copies with the container. Assignment and reconstruction release the destination's old owned storage before the new value lands.
A heap-bearing verb result is caller-owned. If the verb already owns the value it answers, ownership transfers to the caller. If it answers a borrowed parameter, field, or list item, the return boundary clones that value so the caller receives independent storage. This is not a blanket clone of every return. See Safe by design for the working model; the Host & C interop reference will specify the ABI boundary separately.