Vert Quick Reference
Lexical
- Comments:
# to end of line. No block comments. No semicolons; statements separated by whitespace/newlines. Blocks close withend. - Numbers:
42,1_000_000,0xFF,3.14,1.5e3,12.34f64(f64 suffix on float literals only). Ints default toint, floats tofloat. - Strings:
"..."with\escapes;{expr}inside a string interpolates. - Runes:
'A','😀', escapes'\t' '\n' '\"' '\\'. - Whitespace is significant around postfix syntax and in expression lists — see the disambiguation rules under Containers.
- Other literals:
nil,true,false,{}(empty record/map/set, by context).
Declarations
let x = 42 # inferred
let x: float = 1.0 # annotated
let x: int # declared, assigned later
x := 42 # shorthand for let x = 42
const limit = 100 # immutable (deep: no mutation through const)
let a, b: float, c = (1, 2.0, "s") # tuple destructure (per-name annotations ok)
type Vector2 = {x: float, y: float} # type alias
Reassignment is plain = (no let). Multi-assign: a, b = (1, 2).
Compound assign += -= *= /= %= works on variables, arr[i], and obj.field.
Types are inferred everywhere an annotation is omitted (locals, returns,
generics); annotate to constrain or document.
Types
| syntax | meaning |
|---|---|
int int64 float float64 bool byte rune string void |
primitives (float is 32-bit, int 32-bit; *64 widen) |
T? |
optional |
T[] |
array |
[K]V |
map |
set[T] |
set |
(A, B, ...) |
tuple (2+ elements) |
{f: T, g: U} |
structural record |
task(T) |
spawned-task handle |
Name / Name[A, B] |
class/interface/enum/alias, optionally generic |
*T / *const T |
pointer — rejected in user code ("Raw pointers are not part of Vert"); internal only, as is &x address-of |
Operators
- Arithmetic:
+ - * / %, unary-.+also concatenates strings, arrays, maps (right wins duplicate keys), and unions sets;-on sets is difference. - Comparison:
== != < > <= >=, chainable (0 <= i < n). Records/arrays/ tuples/maps/sets/enums compare structurally;uniqueclass instances by identity. - Logic:
and or not. On failable operands,orpicks the first success. - Postfix
?: asserts/propagates a failable or optional (see Failure). ?.: safe field access on optionals —point?.xyieldsint?.a..b: inclusive range, only as aforiteration source.- Precedence:
or<and< comparisons <+ -<* / %<not/unary < postfix.
Functions
func add(a: int b: int): int return a + b end
func log(msg: string, level = 1, color = "white"): string ... end
log("hi" level: 2) # named args skip/reorder defaults
func inner_demo(): int
func inner(): int return 1 end # nested funcs close over locals
let f = inner # functions are first-class values
return f()
end
- Signature:
func name(params) <effect>...: ret_type body end. Return type and effects are optional (inferred).returnwith no value only beforeend/else/elseif. - Multiple returns:
return a, byields a tuple. - Params:
name: type,name: type = default,name = default, barename. Commas between parameters and call arguments are optional.
Control flow
Everything is an expression: if, case, loops, and do blocks yield values
(a loop yields the array of its body values).
if cond then ... elseif cond2 then ... else ... end
let r = if ok? then 1 else 2 end
if v := lookup(), v > 0 then print(v) end # header clauses: comma-separated
# conditions and := bindings; each
# must succeed for the body to run
while cond do ... end # same header-clause form
loop ... break ... end # infinite until break
do ... last_expr end # block expression → last value
for x in values do ... end
for i in 0..3 do ... end # inclusive range
for k, v in map do ... end # 2 bindings: map/array idx
for v in 1..10, v != 3, v != 7 do ... end # filter clauses
for a in xs, b in ys do ... end # nested (cross product)
let doubled = for v in 1..5 do v * 2 end # comprehension
case is the pattern match. Subjects are enums (exhaustive) or runes;
commas between arms are optional:
let label = case dir
Direction.north => "N",
Direction.south => "S",
_ => "other" # wildcard; otherwise all variants required
end
case r
Result.ok(value) => value, # bind payload fields positionally
e := Result.err => do print(e.message) 0 end, # bind whole matched value
end
let n = case ch 'a' => 1, _ => 2 end # rune subject with literal patterns
Failure (<decides>) and effects
A <decides> function either returns or fails. Failure is transactional:
writes made before fail roll back.
func validate(age: int) <decides>: int
if 0 <= age <= 150 then return age else fail end
end
let v = option{validate(10)} # int? — value on success, nil on fail
let ok = bool{validate(10)?} # true/false
let n = validate(10)? + 1 # ? propagates failure to enclosing context
let w = primary() or backup() # first success wins
if validate(x) then ... end # if/while headers are failure contexts
Effect annotations, <name> after the param list (multiple allowed,
all inferred when omitted; annotations are checked against inference):
<succeeds>— cannot fail (default).<decides>— may fail.<suspends>— may suspend; mutually exclusive with<decides>.<converges>— terminates (default);<computes>— pure but may diverge.<reads>/<writes>— reads/writes shared state (module bindings, captured mutables, class fields). Local mutation stays pure.<allocates>— creates managed refs (class instances, closures, spawn).<transacts>— shorthand for reads+writes+allocates+diverges.<no_rollback>— opts out of failure rollback.
Optionals
let v: int? = option{42}
let none: int? = nil
let sum = v? + 2 # ? unwraps (fails if nil)
let has = bool{v?}
let x = point?.x # safe access → optional
Containers
let a = [1 2 3] # array; [] is empty; commas are optional
let s = a.slice(1, 3) # a.length(), push, etc. — see /std + methods
let m: [string]int = {"Ada" = 10 "Bea" = 20}
m["Ada"] = 30 m.delete("Bea") m.length()
let k: [Enum]int = {E.a = 1, node = 2} # map keys are arbitrary exprs
# (strings, enums, unique class instances, ...)
let st: set[int] = {1, 2, 2} # dedupes
let st2 = {1 2 3} # unannotated {e e ...} is a set
let hit = bool{st[2]} # set/map indexing is failable
let t = (1 2.0 "three") # tuple; (x,) is a 1-tuple
let r = {x: 0.0 y: 0.0} # record literal: identifier keys
type P = {x: int, y: int}
let p: P = {3 4} # contextual positional record initializer
Braces disambiguate by content: {} empty, {f: e} record, {e = e} map,
{e, e} / {e e} set — except {e e} against an expected record type is a
positional record initializer (values in declaration order). Record keys
cannot be strings or expressions (use a map).
Whitespace is significant in expression lists. Arrays, tuples, calls,
records, maps, sets, case arms, parameters, patterns, enum variants, and type
lists accept commas or whitespace between items, but one list cannot mix the
two styles: (x, y z) is invalid. Disambiguation rules:
- Postfix must attach to its operand:
f(x)calls,a[i]indexes,P{x: 1}constructs — whilef (x),a [i],P {x: 1}are two separate expressions in a list context. - A space before
.,?., or postfix?is a parse error (a .f,r ?.x,v ?); space after the dot is fine (a. f). - Infix vs prefix by spacing:
x - yandx-ysubtract;x -yisxthen-yas two list items.
Indexing a[i] / m[k] is failable (makes the function <decides> unless
handled). Iteration order and all semantics are deterministic.
Concurrency (<suspends>)
Scheduling is deterministic (wake order = task creation order).
sleep(seconds) suspends (sleep(0) yields).
Structured forms — child tasks cannot escape the enclosing lexical scope; any still running when the scope exits are cancelled:
sync a() b() end # run arms concurrently, wait for all → tuple of results
race slow() fast() end # first result wins; losers are cancelled immediately
rush a() b() end # first result wins; losers keep running in the
# enclosing scope until it exits
branch announce() end # fire-and-continue: no result, caller doesn't block,
# child is still scoped to the caller
Unstructured — the task outlives its creating scope:
let h: task(int) = spawn work() # typed handle, escapes scope
h.Await() h.Cancel() # cancellation unwinds the child's defers
defer expr runs at lexical scope exit, LIFO; discarded if a speculative
context fails. sync/race/rush/sleep require <suspends>;
spawn/branch don't propagate it to the caller.
Enums
enum Status active, inactive end # commas optional
enum Result ok{value: int} err{message: string} end # payload variants
let s: Status = Status.active # always qualified
let r = Result.ok{value: 1}
Closed, no bare variants, ==/!= comparable, usable as map/set keys.
Classes and interfaces
class Character
name: string # field
health: int = 100 # field with default
speed := 1.0 # := infers field type
const max_health: int = 100
func take_damage(amount: int): void
Self.health = Self.health - amount # Self required for members
return
end
end
let hero = Character{name: "A"} # construction: named fields;
# defaulted fields optional
abstract class Entity # modifiers: abstract | final | unique
abstract func describe(): string # abstract method: no body
end
class Player extends Entity
override func describe(): string return "p" end # override required;
end # `final func` / `override final func` seal
unique class Node value: int end # identity equality; usable as map/set key
# (non-unique classes are rejected as keys)
class Box[T] value: T end # generics; constraint: [T: Interface]
let b = Box[int]{value: 1} # explicit args (often inferred)
class IntBox extends Box[int] end
interface Named
name: string
func label(): string
end
interface Fancy extends Named end # interface inheritance (multi via ,)
class Item implements Named ... end # implements list via ,
let n = Named{item} # dynamic cast to class/interface
super calls the base implementation. Class instances are reference values
(aliases share mutations); records/tuples are values.
Conversions and captures
int{x} int64{x} float{x} float64{x} # numeric conversions
byte{65} byte{'A'} # from int or rune
string{42} string{'😀'} string{bytes} # primitives/rune/array[byte] → string
array{"text"} # string → byte[]
bool{failable?} option{failable} # capture failure as bool / optional
Name{expr} # dynamic cast (classes/interfaces)
Builtins and modules
print(a, b, ...) (tab-separated), sleep(seconds).
import /std/math as math # math.sqrt, ...
import /std/text as text # text.get, text.insert, ...
Only absolute paths and /std/* modules; no relative imports. A module alias
is a value (let m = math) but has no declarable type, so it can't pass
through function parameters.