Vow Grammar Reference¶
Complete grammar for the Vow programming language. Vow source files use the .vow extension.
Line comments. // starts a line comment extending to end of line. Comments are stripped during lexing and never enter the token stream. Block comments (/* */) are not supported. Machine-relevant intent belongs in contracts; comments are for non-semantic rationale.
Module Declaration¶
Every file begins with a module declaration:
<Name> is a PascalCase identifier. There is no semicolon.
Use Declarations¶
Import other modules with dot-separated paths:
This resolves to <rootdir>/foo/bar.vow relative to the main source file.
Const Declarations¶
Named constants with compile-time values:
Supported value forms: integer literals, boolean literals, negated integer literals. Constants are inlined at every use site (zero runtime cost). The type must be any of the 10 integer types (i8, i16, i32, i64, i128, u8, u16, u32, u64, u128) or bool. Integer constants are subject to the same compile-time range check as integer literals. Constants are referenced by name in expressions like any other identifier.
Functions¶
Pure Function¶
Function with Effects¶
Effects appear in brackets after the return type: [io], [read, write], [io, panic].
Function with Vow Block¶
The vow block sits between the signature and the body. Clauses:
- requires: <expr> — precondition (blame: Caller)
- ensures: <expr> — postcondition (blame: Callee); use result for the return value
- invariant: <expr> — loop invariant (blame: Callee)
Multiple clauses are separated by commas:
fn clamp(x: i64, lo: i64, hi: i64) -> i64 vow {
requires: lo <= hi,
ensures: result >= lo,
ensures: result <= hi
} {
if x < lo { lo } else { if x > hi { hi } else { x } }
}
Where Clauses (Refinement Types on Parameters)¶
fn safe_sub(a: i64 where a >= 0, b: i64 where b >= 0) -> i64 vow {
requires: a >= b,
ensures: result >= 0
} {
a - b
}
where constraints on parameters become additional requires in verification. Each where clause can only reference its own parameter — it cannot reference other parameters.
Public Functions¶
Types¶
Primitive Types¶
| Type | Description |
|---|---|
i8 |
8-bit signed integer |
i16 |
16-bit signed integer |
i32 |
32-bit signed integer |
i64 |
64-bit signed integer |
i128 |
128-bit signed integer (verifier may time out; see below) |
u8 |
8-bit unsigned integer |
u16 |
16-bit unsigned integer |
u32 |
32-bit unsigned integer |
u64 |
64-bit unsigned integer |
u128 |
128-bit unsigned integer (verifier may time out; see below) |
f32 |
32-bit float (limited support — avoid in contracts) |
f64 |
64-bit float (limited support — avoid in contracts) |
bool |
Boolean |
() |
Unit type |
! |
Never type (diverges) |
There is no isize/usize. Vow targets 64-bit only; Vec::len() returns i64,
indices are i64. This is deliberate — it preserves binary fixed point
reproducibility across compilations. See ADR 0001.
128-bit verification: i128/u128 arithmetic codegens via Cranelift's
I128 and verifies via ESBMC's __int128. Predicates over 128-bit values may
exceed configured SMT solver timeouts. Such proofs use the ordinary verifier
controls and retain fail-closed timeout or unknown outcomes; there is no
type-specific verification opt-out. Never weaken contracts to fit the verifier.
Struct field layout: every struct field up to 64 bits wide occupies one
8-byte slot regardless of declared type (narrow ints are padded); i128/u128
fields occupy two consecutive 8-byte slots (16 bytes). There is no packing or
natural-alignment layout today; FFI structs that need a specific C layout must
shim through Vec<u8> or extern wrappers.
Built-in Parameterized Types¶
| Type | Description |
|---|---|
Vec<T> |
Growable array |
Option<T> |
Optional value (Some/None) |
Result<T, E> |
Success or error |
String |
UTF-8 string (backed by Vec |
HashMap<K, V> |
Key-value map (linear scan) |
BTreeMap<K, V> |
Sorted key-value map (binary search; ascending iteration). K must be i64; V may be any non-linear type |
User-Defined Types¶
Structs and enums (see below).
Literals¶
Integer Literals¶
Unsuffixed integer literals default to i64 in expression position, and
context-coerce to any of the 10 integer types when the
surrounding context fixes one — let bindings, function arguments, struct
fields, and the typed operand of an arithmetic, bitwise, or comparison
operator. The same coercion applies to constant expressions composed entirely
of unsuffixed integer literals (e.g. 1 + 2, 1 << 3, -5).
Out-of-range literals in a typed context are a compile-time error:
let x: u8 = 300; // error: LiteralOutOfRange — 300 does not fit in u8
let y: i8 = 200; // error: LiteralOutOfRange — i8 range is -128..=127
Suffixed integer literals force the type at the literal:
Suffixed forms are supported for all 10 integer widths. They override context coercion and are still subject to the same compile-time range check.
Float Literals¶
Boolean Literals¶
String Literals¶
Supported escape sequences: \n, \t, \r, \\, \", \0.
String literals have type String and are backed by a read-only static
descriptor. Passing or returning a literal does not allocate. To obtain a
mutable, arena-owned copy, use String::from("...").
Operators¶
Wrapping Arithmetic (default)¶
| Operator | Meaning |
|---|---|
+ |
Add (wrapping) |
- |
Sub (wrapping) |
* |
Mul (wrapping) |
/ |
Div (wrapping) |
% |
Rem (wrapping) |
Wrapping operators silently wrap on overflow. For unsigned operands, including
u8, division and remainder use unsigned semantics.
Checked Arithmetic¶
| Operator | Meaning |
|---|---|
+! |
Add (checked) |
-! |
Sub (checked) |
*! |
Mul (checked) |
/! |
Div (checked) |
%! |
Rem (checked) |
Checked operators abort with ArithmeticOverflow on overflow.
Saturating Arithmetic¶
Saturating arithmetic uses named compiler intrinsics rather than a third
operator family. The u8 intrinsics are:
| Function | Signature | Behavior |
|---|---|---|
add_sat_u8 |
fn(a: u8, b: u8) -> u8 |
clamps sums above 255 to 255 |
sub_sat_u8 |
fn(a: u8, b: u8) -> u8 |
clamps differences below 0 to 0 |
mul_sat_u8 |
fn(a: u8, b: u8) -> u8 |
clamps products above 255 to 255 |
These functions are pure and have direct verifier semantics; they do not lower to wrapping arithmetic.
Comparison Operators¶
| Operator | Meaning |
|---|---|
== |
Equal |
!= |
Not equal |
< |
Less than |
<= |
Less than or equal |
> |
Greater than |
>= |
Greater than or equal |
Bitwise Operators¶
| Operator | Meaning |
|---|---|
& |
Bitwise AND |
\| |
Bitwise OR |
^ |
Bitwise XOR |
<< |
Left shift |
>> |
Right shift |
Bitwise & | ^ require integer operands of the same type and work on all 10
integer widths. >> is arithmetic (sign-extending) for signed types
(i8..i128) and logical (zero-extending) for unsigned types
(u8..u128).
Shift count type. The right operand of << and >> is u32. Unsuffixed
integer literals on the right side context-coerce to u32: given
let x: u8 = ..., x << 3 is well-typed (3 coerces to u32). The left
operand keeps its own integer type; the shift result has the left operand's
type.
Shift count range. A const-expression shift count >= bit-width(LHS) is a
compile-time error (ShiftCountOutOfRange). For example, (x: u8) << 8 does
not compile. Dynamic shift counts (x << n where n is not a const
expression) get a contract on the operation that ESBMC checks: the count must
be less than the LHS width at the point of the shift.
Unsuffixed literal coercion still applies for &, |, ^ operands: with
let x: u64 = ..., 3 & x and x | 0xff type-check because the literal
side coerces to u64. Use a suffix to force a different type explicitly.
Logical Operators¶
| Operator | Meaning |
|---|---|
&& |
Logical AND (short-circuit) |
\|\| |
Logical OR (short-circuit) |
! |
Logical NOT |
&& and || use short-circuit evaluation: for a && b, b is only evaluated if a is true; for a || b, b is only evaluated if a is false.
Operator Precedence¶
From loosest to tightest, Vow follows the usual C/Rust precedence for logical and bitwise operators:
||, &&, comparisons (== != < <= > >=), |, ^, &, << >>, + -, * / %
Unary -, !, &, and ? bind tighter than every binary operator.
Single & is overloaded by position: prefix &expr is borrow, while infix lhs & rhs is bitwise AND.
Unary Operators¶
| Operator | Meaning |
|---|---|
- |
Negation (not allowed on unsigned types) |
! |
Logical NOT |
& |
Borrow |
? |
Unwrap (propagate error) |
Type Cast¶
as is widening-only across integer types. Any narrower integer can be
cast to any wider integer; signed sources sign-extend, unsigned sources
zero-extend:
let a: i32 = -1;
let b: i64 = a as i64; // sign-extend: -1_i64
let c: u8 = 200;
let d: u64 = c as u64; // zero-extend: 200_u64
let e: u32 = 1;
let f: i64 = e as i64; // unsigned-to-signed widening, value preserved
as between signed and unsigned of the same width is also allowed
(machine-level bit reinterpretation): i64 as u64, u64 as i64, i32 as u32,
etc.
Narrowing via as is a compile-time error (NarrowingCastNotAllowed):
To narrow, use a named intrinsic that makes the intent explicit. For every
narrowing pair (src, tgt) the compiler exposes three free functions:
| Intrinsic | Behavior on out-of-range input |
|---|---|
<src>_to_<tgt>_try(x) -> Option<tgt> |
returns Option::None |
<src>_to_<tgt>_wrap(x) -> tgt |
truncates (low bits, two's-complement) |
<src>_to_<tgt>_sat(x) -> tgt |
clamps to the target type's range |
Example:
let big: i64 = 300;
match i64_to_u8_try(big) {
Option::Some(b) => use_byte(b),
Option::None => fallback(),
}
These intrinsics are emitted by the compiler so ESBMC sees their semantics directly in the verification C model.
For the u8 target, the available narrowing source types are i16, i32,
i64, i128, u16, u32, u64, and u128. Each source provides all three
forms, for example u16_to_u8_try, u16_to_u8_wrap, and u16_to_u8_sat.
For the i32 target, the available narrowing source types are i64, u32,
and u64, each providing all three forms: i64_to_i32_try/_wrap/_sat,
u32_to_i32_try/_wrap/_sat, and u64_to_i32_try/_wrap/_sat.
The remaining executable sub-64-bit targets expose these complete families:
| Target | Narrowing source types |
|---|---|
i8 |
i16, u16, i32, u32, i64, u64 |
i16 |
i32, u32, i64, u64 |
u16 |
i32, u32, i64, u64 |
u32 |
i64, u64 |
Every listed source/target pair provides _try, _wrap, and _sat. Same-width
signedness changes use as; they are bit reinterpretations, not narrowing.
No implicit conversions: i64 + u64 and u8 + i32 are type errors. The
operands must already have the same type. The compiler does not coerce
across integer types at operator sites — only literals coerce, per the
Integer Literals rules.
Let Bindings¶
Immutable¶
Bindings are immutable by default. Reassigning a binding that was not declared
mut is a compile error (ImmutableAssignment). mut is required only for
whole-binding reassignment x = e; field writes (s.f = e) and index writes
(v[i] = e) are permitted through any binding and do not require the base to be
mut.
Mutable¶
A let mut binding that is never reassigned is a compile error (UnusedMut) —
drop the mut. Because only whole-binding reassignment counts as a use of mut,
a binding mutated solely via s.f = e, v[i] = e, or a method call should be
declared let, not let mut.
Pattern Destructuring¶
Control Flow¶
If / Else¶
if/else is an expression — both branches must have the same type. There is no else if keyword; nest if inside else:
While Loop¶
While Loop with Invariant¶
For-Each Loop¶
Iterates over each element of a Vec<T>. The loop variable x is bound to each element in turn. Desugars to a while loop with index arithmetic — zero verification overhead.
For-Each Loop with Invariant¶
Loop (Infinite)¶
loop creates an infinite loop. The expression returns the type of the break value:
ESBMC cannot verify unbounded loop constructs — use while with invariants for verifiable loops.
Break¶
break exits the innermost loop. Inside loop, break value sets the loop's result:
break; // exit while or loop (loop returns Unit)
break value; // exit loop with a value (only inside loop, not while)
Continue¶
continue skips the remaining statements in the current loop iteration and jumps back to the loop header:
Inside while and loop, continue emits back-edge values for any mutated variables. Inside for, it also advances the loop index.
Return¶
Struct Definitions¶
Linear Structs¶
Linear struct values carry a linear obligation. The obligation must either be consumed before the value's owning region closes or transferred to the caller by returning the value.
Owned enum wrappers inherit that obligation transitively. A user enum,
Option<T>, or Result<T, E> is linear when one of its owned payload paths is
linear; matching such a value consumes the wrapper exactly once and transfers
the obligation to the selected bound payload. References remain borrows and do
not become linear owners. Collection types do not acquire linear ownership from
their element type; their separate non-linear-element restrictions still apply.
An unbound _ match catchall cannot discard a still-reachable linear payload:
every variant that owns a linear payload must first have an explicit arm that
binds and consumes or transfers that payload.
Struct fields cannot own linear values, even when the containing struct is
linear, because field access does not provide move-out semantics. Allowing an
owned field would let repeated reads transfer the same obligation more than
once. Borrowed references and collection fields do not become linear owners
under this rule.
Struct Literals¶
Struct literal names must be PascalCase:
Field Access¶
Field Assignment¶
Passing Semantics¶
Structs are heap-allocated. A struct value is a pointer to a heap region, so passing a struct to a function passes the pointer — the function operates on the same heap data, not a copy. Field assignments inside the called function are visible to the caller:
fn shift_right(p: Point, dx: i64) {
p.x = p.x + dx;
}
fn main() -> i32 [io] {
let p: Point = Point { x: 0, y: 0 };
shift_right(p, 5);
print_i64(p.x); // 5 — mutation visible to caller
0
}
This enables in-place mutation patterns (e.g., make/unmake in search trees) without cloning. The same aliasing semantics apply when structs are stored in containers — see Indexing. To avoid aliasing, construct a fresh struct literal with the desired field values.
Note: For linear owner types (a linear struct or an owned enum wrapper that
contains one), passing the value to a function consumes it; the caller cannot
access it afterward. Returning a linear value transfers the obligation to the
caller, so this is the normal way to hand an updated linear value back out of a
function.
Enum Definitions¶
Variant kinds: unit (Empty), tuple (Circle(i64)), struct (Named { x: i64 }).
Enum Construction¶
let s: Shape = Shape::Circle(5);
let none: Option<i64> = Option::None;
let some: Option<i64> = Option::Some(42);
Built-in Enums¶
Option<T> has variants Some(T) and None.
Result<T, E> has variants Ok(T) and Err(E).
Pattern Matching¶
Match is an expression. The scrutinee must have an enum type, including an
applied built-in enum such as Option<T> or Result<T, E>. All arms must
return the same type. Patterns must be exhaustive.
Pattern Kinds¶
| Implemented pattern | Example |
|---|---|
| Wildcard | _ |
| Immutable identifier binding | value |
| Qualified enum variant (unit) | Option::None |
| Qualified enum variant (tuple payload) | Option::Some(value) |
Tuple-variant patterns must provide exactly one payload binding for every
declared payload, and each binding may be only _ or an immutable identifier.
The qualified enum name must match the scrutinee's enum; a variant from another
enum neither binds payloads nor counts toward exhaustiveness.
Nested payload destructuring is not implemented. A catchall _ or immutable
identifier arm must be the final arm because it matches every enum value.
For an enum that can own linear payloads, _ is allowed only after explicit
arms have handled every variant with a linear payload; otherwise the catchall
would silently discard an outstanding linear obligation.
Mutable identifier, literal (integer, boolean, or string), tuple, struct,
enum-struct, or-pattern, unqualified enum-variant, and nested payload patterns
are not implemented. Parsed unsupported forms produce
error[UnsupportedPattern]; forms that the parser cannot represent produce
error[UnexpectedToken]. Both are compile-time failures and no executable is
produced.
Method Calls¶
Vec Methods¶
| Method | Signature |
|---|---|
Vec::new() |
() -> Vec<T> |
Vec::from_raw_parts_copy(ptr, len) |
(i64, i64) -> Vec<T> for flat scalar T |
.push(val) |
(T) -> () |
.pop() |
() -> () |
.len() |
() -> i64 |
.clear() |
() -> () — frees buffer, resets to empty |
.truncate(n) |
(i64) -> () — shrinks to n elements, frees excess memory |
v[i] |
Index read — copies slot value; aliases heap types (panics if out of bounds) |
v[i] = val |
Index write — copies value into slot |
String Methods¶
| Method | Signature |
|---|---|
String::from(s) |
(String) -> String — mutable copy |
String::new() |
() -> String |
String::from_raw_parts_copy(ptr, len) |
(i64, i64) -> String |
.len() |
() -> i64 |
.byte_at(i) |
(i64) -> i64 |
.push_byte(b) |
(i64) -> () |
.push_str(s) |
(String) -> () |
.clear() |
() -> () — frees buffer, resets to empty |
.contains(s) |
(String) -> bool |
.eq(s) |
(String) -> bool |
.substring(start, end) |
(i64, i64) -> String |
.parse_i64() |
() -> Option<i64> |
.parse_u64() |
() -> Option<u64> |
HashMap Methods¶
| Method | Signature |
|---|---|
HashMap::new() |
() -> HashMap<K, V> |
.insert(k, v) |
(K, V) -> () |
.get(k) |
(K) -> V |
.contains_key(k) |
(K) -> bool |
.remove(k) |
(K) -> () |
.len() |
() -> i64 |
BTreeMap Methods¶
Keys must be i64 (K violations raise BTreeMapKeyTypeMustBeI64). Values may be any
non-linear type — primitives, structs, Vec<T>, Option<T>, or nested combinations.
A V that is or transitively contains a linear struct is rejected with
BTreeMapValueMustBeNonLinear, because the runtime/verifier shift values bitwise and
would silently duplicate a linear obligation.
Storage is two parallel sorted arrays (binary-search lookup, sorted-insert writes).
Iteration order is ascending by key and is deterministic across runs and compilers —
prefer BTreeMap over HashMap for any map whose iteration affects compiler output.
| Method | Signature |
|---|---|
BTreeMap::new() |
() -> BTreeMap<K, V> |
.insert(k, v) |
(K, V) -> Option<V> (returns the previous value bound to k, if any) |
.get(k) |
(K) -> Option<V> (returns the value bound to k, or None) |
.contains(k) |
(K) -> bool |
.len() |
() -> i64 |
Option Methods¶
| Method | Signature |
|---|---|
.unwrap() |
() -> T (panics on None; requires [panic] effect) |
The ? operator on Option<T> or Result<T, E> propagates None/Err to the caller (the calling function must return Option or Result).
Indexing¶
Indexing uses copy semantics: v[i] copies the 8-byte slot value and v[i] = val copies a value into the slot. The base container is not consumed.
For primitive types (i64, bool), this is a genuine value copy — the result is independent of the container. For heap types (Vec<T>, String, structs, enums), the 8-byte slot holds a pointer, so indexing copies the pointer, creating an alias. Both the container slot and the local variable point to the same heap data:
let buckets: Vec<Vec<i64>> = Vec::new();
buckets.push(Vec::new());
let b: Vec<i64> = buckets[0]; // b aliases buckets[0]
b.push(42); // visible through buckets[0]
This aliasing is the intended behavior for arena and hash-table patterns where bucket contents are read and mutated repeatedly through index access.
Extern Blocks¶
Declare external C functions (a vow contract block is required):
extern "C" vow {
requires: fd >= 0
ensures: return >= 0
}
{
fn write(fd: i32, ptr: i64, len: i64) -> i64 [io]
}
Omitting the vow block produces a MissingContract error (see errors.md).
Type Aliases¶
Effect System¶
Effects are explicit. Every function declares which side effects it may perform. Pure functions (no effects) need no annotation.
Effect Types¶
| Effect | Meaning |
|---|---|
io |
Standard I/O (print, stdin, network) |
read |
File system reads |
write |
File system writes |
panic |
May panic (unwrap, etc.) |
unsafe |
Unsafe operations (FFI, raw memory) |
Each effect is independent — io is not a superset of read or write.
Propagation¶
A function must declare every effect that any function it calls may produce:
If caller omitted [io], the type checker would emit EffectViolation.
Contract Purity¶
Contract expressions (requires, ensures, invariant) must be pure — they cannot call effectful functions.
Builtin Function Signatures¶
FFI Wrapper Intrinsics¶
| Function | Signature | Effects |
|---|---|---|
pin_to_root |
fn(value: String) -> String and fn<T>(value: Vec<T>) -> Vec<T> for flat scalar T |
[] |
pin_to_root is a compiler intrinsic, not a user-defined generic. Each call site is monomorphised from the argument type. It always deep-copies the supported heap value into root storage; it does not inspect descriptor tags and does not claim idempotency. The current supported forms are String and Vec<T> where T is a flat scalar slot type (i*, u*, f32, f64, bool). Pointer-containing payloads, user structs, enums, and maps require hand-written deep-copy wrappers at the FFI boundary.
String::from_raw_parts_copy(ptr: i64, len: i64) copies len bytes from a raw C pointer into a fresh String. Vec::from_raw_parts_copy(ptr: i64, len: i64) copies len flat scalar slots into a fresh Vec<T>. The surface length type is i64; the code generator converts pointer and length values to the platform pointer-sized ABI type at the FFI boundary. Both helpers have a FreshInCaller return summary.
For pointer-containing C payloads, a wrapper must be written per type: call the extern, recursively copy every Vow-owned heap subobject into the target region, free every C-owned pointer according to the extern's ownership contract, then return the Vow-placed value. A bytewise copy of a pointer-containing payload is unsound because it preserves stale pointers into C-owned storage.
Print / IO¶
| Function | Signature | Effects |
|---|---|---|
print_str |
fn(s: String) -> () |
[io] |
print_i64 |
fn(v: i64) -> () |
[io] |
print_u64 |
fn(v: u64) -> () |
[io] |
eprintln_str |
fn(s: String) -> () |
[io] |
Debug¶
| Function | Signature | Effects |
|---|---|---|
debug_str |
fn(s: String) -> () |
[] |
debug_i64 |
fn(v: i64) -> () |
[] |
debug_u64 |
fn(v: u64) -> () |
[] |
Debug print semantics: Debug prints are effect-free and callable from pure functions. In debug and sanitize modes (--mode debug, --mode sanitize), they write to stderr. In release and profile modes, the debug call itself is not emitted — no function call occurs. However, argument expressions are still evaluated (a direct literal such as "label" is static, while String::from("label") still allocates a mutable copy). They are also no-ops during verification. Use them to trace values inside pure kernel code without restructuring the effect hierarchy.
Filesystem¶
| Function | Signature | Effects |
|---|---|---|
fs_read |
fn(path: String) -> String |
[read] |
fs_open |
fn(path: String) -> i64 |
[read] |
fs_read_line |
fn(handle: i64) -> String |
[read] |
fs_status |
fn(handle: i64) -> i64 |
[read] |
fs_close |
fn(handle: i64) -> i64 |
[read] |
fs_write |
fn(path: String, data: String) -> i64 |
[write] |
fs_exists |
fn(path: String) -> i64 |
[read] |
fs_mkdir |
fn(path: String) -> i64 |
[io] |
fs_listdir |
fn(path: String) -> Vec<String> |
[read] |
fs_remove |
fn(path: String) -> i64 |
[io] |
fs_remove_dir |
fn(path: String) -> i64 |
[io] |
fs_is_dir |
fn(path: String) -> i64 |
[read] |
fs_is_symlink |
fn(path: String) -> i64 |
[read] |
fs_rename |
fn(old: String, new: String) -> i64 |
[io] |
String Operations¶
| Function | Signature | Effects |
|---|---|---|
string_substr |
fn(s: String, start: i64, len: i64) -> String |
[] |
string_split |
fn(s: String, delim: String) -> Vec<String> |
[] |
string_starts_with |
fn(s: String, prefix: String) -> i64 |
[] |
string_ends_with |
fn(s: String, suffix: String) -> i64 |
[] |
string_matches_literal_at |
fn(s: String, pos: i64, literal: String literal) -> i64 |
[] |
string_trim |
fn(s: String) -> String |
[] |
string_to_upper |
fn(s: String) -> String |
[] |
string_to_lower |
fn(s: String) -> String |
[] |
string_replace |
fn(s: String, from: String, to: String) -> String |
[] |
string_join |
fn(parts: Vec<String>, sep: String) -> String |
[] |
Conversion¶
Formatting uses two baselines; widen via as for narrower types:
| Function | Signature | Effects |
|---|---|---|
int_to_string |
fn(v: i64) -> String |
[] |
uint_to_string |
fn(v: u64) -> String |
[] |
i64_to_string |
fn(v: i64) -> String (alias of int_to_string) |
[] |
Parsing exposes a try-form for every integer width:
| Function | Signature |
|---|---|
parse_i8 |
fn(s: String) -> Option<i8> |
parse_i16 |
fn(s: String) -> Option<i16> |
parse_i32 |
fn(s: String) -> Option<i32> |
parse_i64 |
fn(s: String) -> Option<i64> (also see String.parse_i64()) |
parse_i128 |
fn(s: String) -> Option<i128> |
parse_u8 |
fn(s: String) -> Option<u8> |
parse_u16 |
fn(s: String) -> Option<u16> |
parse_u32 |
fn(s: String) -> Option<u32> |
parse_u64 |
fn(s: String) -> Option<u64> (also see String.parse_u64()) |
parse_u128 |
fn(s: String) -> Option<u128> |
Each parse_X returns Option::None for malformed input, empty strings, or
values outside the target type's range. Parsing never substitutes a numeric
sentinel for failure; callers that need a fallback must choose it explicitly
when handling Option::None.
In particular, parse_i8, parse_i16, parse_u8, parse_u16, parse_i32,
and parse_u32 enforce their exact signed or unsigned fixed-width ranges.
Narrowing intrinsics (per Type Cast): for every narrowing
pair the compiler emits <src>_to_<tgt>_try, <src>_to_<tgt>_wrap, and
<src>_to_<tgt>_sat free functions with the semantics described in that
section.
Collections¶
| Function | Signature | Effects |
|---|---|---|
vec_sort |
fn(v: Vec<i64>) -> Vec<i64> |
[] |
Time¶
| Function | Signature | Effects |
|---|---|---|
time_unix |
fn() -> i64 |
[io] |
time_unix_ms |
fn() -> i64 |
[io] |
System¶
| Function | Signature | Effects |
|---|---|---|
num_cpus |
fn() -> i64 |
[io] |
memory_root_arena_bytes |
fn() -> u64 |
[io] |
memory_peak_bytes |
fn() -> u64 |
[io] |
memory_alloc_count_since_start |
fn() -> u64 |
[io] |
num_cpus() returns the number of available logical CPUs (from std::thread::available_parallelism), or 1 if the query fails. Used to size worker pools (e.g. the default --verify-jobs value).
memory_root_arena_bytes() returns the current bytes retained by root-region
arena chunks. It is a gauge, not a monotone counter: adding a root chunk raises
it, while reclaiming an abandoned single-resident oversized root chunk during
backing growth lowers it. memory_peak_bytes() returns the peak live bytes
retained by all open arena chunks since process start.
memory_alloc_count_since_start() returns the number of successful Vow arena
allocation requests since process start. Peak bytes and allocation count are
monotone non-decreasing and saturate at u64::MAX rather than wrapping. These
queries do not allocate; they are effectful because they observe runtime process
state.
Encoding¶
| Function | Signature | Effects |
|---|---|---|
hex_encode |
fn(data: Vec<u8>) -> String |
[] |
hex_decode |
fn(s: String) -> Vec<u8> |
[] |
Input¶
| Function | Signature | Effects |
|---|---|---|
args |
fn() -> Vec<String> |
[read] |
stdin_read |
fn() -> String |
[read] |
stdin_read_line |
fn() -> String |
[read] |
stdin_ready |
fn() -> bool |
[read] |
Process Management¶
| Function | Signature | Effects |
|---|---|---|
process_exit |
fn(code: i64) -> ! |
[io] |
process_run |
fn(cmd: String, args: Vec<String>) -> i64 |
[io] |
process_get_stdout |
fn() -> String |
[io] |
process_get_stderr |
fn() -> String |
[io] |
process_start |
fn(cmd: String, args: Vec<String>) -> i64 |
[io] |
process_wait |
fn(pid: i64) -> i64 |
[io] |
process_wait_timeout |
fn(pid: i64, timeout_ms: i64) -> i64 |
[io] |
process_kill |
fn(pid: i64) -> i64 |
[io] |
process_stdout_for |
fn(pid: i64) -> String |
[io] |
process_stderr_for |
fn(pid: i64) -> String |
[io] |
args semantics: args() returns all process arguments including the program name at index 0 (matching C argv and Rust std::env::args() conventions). For ./my_program foo bar, args() returns ["./my_program", "foo", "bar"]. Use args[1] onward for user-supplied arguments. The Vec is empty only if the OS provides no arguments (unusual). Returns an empty String element if an argument is empty (""). Non-UTF-8 arguments are included as-is (byte content preserved).
fs_read semantics: fs_read(path) opens the file at path, reads its entire contents, and returns a String. Returns "" (empty String) on any error (file not found, permission denied, I/O error, non-UTF-8 path). Does not block on regular files. Callers should check result.len() == 0 to detect failure.
Streaming file input: fs_open(path) opens a file for incremental reading and returns a positive handle, or -1 on path/open error. fs_read_line(handle) reads one line from the current cursor and returns it as a String, including the trailing newline when present. It returns "" at EOF, for an invalid handle, or after a read error. A blank line is returned as "\n", so newline-delimited callers can distinguish a real blank line from EOF by content. After fs_read_line(handle) returns "", call fs_status(handle) to distinguish EOF from error: 0 means the handle is open with no EOF/error state, 1 means EOF, and -1 means invalid handle or read error. fs_status(handle) reports the result of the most recent fs_read_line(handle) call on that open handle; read it immediately after a "" return because later reads may update it. fs_close(handle) releases the handle and returns 0 on success or -1 for an invalid/already-closed handle. Long-running programs must close handles they no longer need. All streaming handle operations use the [read] effect, including fs_close, because closing a read handle releases read-stream state and does not mutate filesystem contents. The current runtime stores streaming handles in one process-global table, and fs_read_line holds that table lock while it reads the next line. This keeps the API simple for single-stream file processing, but it is not intended for latency-sensitive concurrent reads from multiple slow handles.
Filesystem return values: fs_write, fs_mkdir, fs_remove, fs_remove_dir, and fs_rename return i64: 0 on success, non-zero on failure. fs_open, fs_status, and fs_close use the streaming status codes above. fs_exists, fs_is_dir, and fs_is_symlink are predicates: they return 1 for true, 0 for false. Errors (null pointer, invalid UTF-8) also return 0, so callers cannot distinguish "false" from "error". fs_is_symlink uses lstat-equivalent semantics: a symlink reports 1 even when its target is a regular file or directory.
string_starts_with / string_ends_with / string_matches_literal_at return values: Return i64: 1 if true, 0 if false.
string_matches_literal_at literal operand: The third argument must be written as a string literal at the call site. The compiler lowers that literal to static bytes plus an explicit byte length, so no temporary String allocation is created and embedded NUL bytes are preserved. Passing a variable or computed String as the third argument is a type-check error (StaticLiteralRequired). Use string_starts_with, string_ends_with, or String methods when the needle must be dynamic.
process_run vs process_start: process_run(cmd, args) runs a subprocess synchronously and returns its exit code. After it returns, process_get_stdout() and process_get_stderr() retrieve the captured output of the most recent process_run call. process_start(cmd, args) launches a subprocess asynchronously and returns a process ID. Use process_wait(pid) to wait for completion and get the exit code, and process_stdout_for(pid) / process_stderr_for(pid) to retrieve output.
process_wait_timeout: process_wait_timeout(pid, timeout_ms) polls a process started with process_start until it exits or the timeout (in milliseconds) elapses. Returns the exit code on completion, -1 on error, or -2 on timeout. After a timeout, the process is still running; use process_kill(pid) to terminate it.
process_kill: process_kill(pid) sends a kill signal to a running process and waits for it to exit. Returns 0 on success, -1 on error. No-op (returns 0) if the process has already completed.
stdin_read vs stdin_read_line: stdin_read() reads the entire stdin stream into a single String (unbounded memory). stdin_read_line() reads one line at a time, including the trailing newline. Returns "" (empty string) at EOF. The returned String is runtime scratch storage valid until the next stdin_read_line() call. Process each line before reading the next one for bounded memory; use pin_to_root(line) before the next read when a line must be stored, returned, passed to a function that may store it, mutated, or otherwise retained. The direct scratch line is read-only. The scratch buffer keeps the largest line capacity seen so far, so memory is bounded by maximum line length rather than total input, but one very large line can retain that capacity for the process lifetime.
let lines: Vec<String> = Vec::new();
let mut line: String = stdin_read_line();
while str_len(line) > 0 {
// Without pin_to_root, lines.push(line) would store the scratch alias, not a copy.
lines.push(pin_to_root(line));
line = stdin_read_line();
}
let mut line: String = stdin_read_line();
while str_len(line) > 0 {
// process line (has trailing \n)
line = stdin_read_line();
}
stdin_ready: stdin_ready() returns true if stdin_read_line() would return immediately without blocking, false otherwise. Uses a non-blocking poll with zero timeout. Use this in computation loops that must remain responsive to external input:
while !stdin_ready() && depth < max_depth {
// continue searching
depth = depth + 1;
}
if stdin_ready() {
let cmd: String = stdin_read_line();
// handle command
}
Canonical Form¶
The canonical printer normalizes source: parse → print → parse is idempotent. Effects are sorted alphabetically, indentation uses 4 spaces, trailing expressions omit semicolons.