This reference describes implemented syntax. See types and errors, modules and packages and standard library for the 0.3 APIs. The design blueprint includes ambitions beyond the implemented release.
Lexical rules
Files are UTF-8. Identifiers match [A-Za-z_][A-Za-z_0-9]*. Line comments
start with //; /* ... */ block comments nest. Semicolons end statements.
Strings are double quoted, allow Unicode, and support \n, \r, \t, \0,
\", and \\. No string interpolation or Unicode escape syntax is implemented.
Reserved syntax words should not be used as binding names. Builtin function
names cannot be redefined globally or as app members.
Types and value semantics
Types: i64, bool, String, Json, Response, void, named structs/enums, List<T>, Map<String,T>, Option<T> and Result<T,E>.
No implicit coercions occur. void is a return type, not a value type. Owned
values copy semantically; the backend clones strings, JSON and structs as
needed. User-visible references, pointers and aliasing are unavailable.
i64 uses checked signed 64-bit arithmetic. Decimal literals have range
0..9223372036854775807; unary negation supplies negative values. Express the
minimum value as -9223372036854775807 - 1. Overflow, division by zero and
invalid remainder unwind as runtime failures. true and false are bool;
null is Json. JSON arrays are JSON values, not statically typed array types.
Object keys are strings and must be unique. Arrays can contain mixed JSON compatible values. All fields/elements must be i64, bool, String or Json:
let data = json({ "items": [1, "two", null], "enabled": true });
Parsed JSON may contain non-i64 numbers; as_i64 checks conversion. No Ryft
floating-point type exists. Json and Response equality are not implemented.
Operators
From lowest to highest precedence:
| Operators | Operands / result |
|---|---|
|| |
bool / bool, short-circuit |
&& |
bool / bool, short-circuit |
== != |
Matching i64, bool, or String / bool |
< <= > >= |
i64 / bool |
+ - |
i64 / i64; + also String + String |
* / % |
i64 / i64 |
Unary ! - |
bool / bool; i64 / i64 |
Call (...), field .name |
Function invocation, struct field |
Binary operators associate left. Parentheses change grouping. Arguments and object fields evaluate in source order. Function names are not first-class values; only named functions can be called.
Functions and structs
struct Product {
id: i64;
name: String;
}
fn product(id: i64) -> Product {
return Product { id: id, name: "Mug" };
}
Functions can be called before their definition. Omitting a return type means
void. Every path through a non-void function must explicitly return that type.
No implicit block return exists. return; ends a void function.
Struct names start with an uppercase letter and cannot shadow builtin types.
Every field must be provided exactly once; read fields with p.name. Recursive
value structs are rejected. Serializable structs provide json(value), .to_json() and fallible .from_json(Json) codecs. Fields of mutable local structs can be assigned. See types and errors for methods, exhaustive matching, typed collections and generic functions.
Locals and control flow
let title: String = "Ryft";
let mut total = 0;
let mut i = 1;
while i <= 10 {
total = total + i;
i = i + 1;
}
if total == 55 {
println(title);
} else {
println("Unexpected total");
}
Locals infer types or accept an annotation. let is immutable; let mut
permits reassignment of the same type. Assignments target local names or fields of mutable local structs.
Parameters are immutable. Duplicate locals and shadowing within a nested block
are rejected. Branch/loop declarations remain local to their block. if and
while conditions require bool. else is an optional braced block; no else-if
sugar, for loop, break, continue or closures are implemented. A loop alone is
not treated as proof that a non-void function returns.
Runtime loop iterations and function entries use cooperative execution checks: 10 million ticks per execution, maximum call depth 128, and an HTTP handler deadline when applicable. These prevent ordinary runaway Ryft loops and recursion from continuing indefinitely; they are not OS CPU or memory limits. Native library calls rely on their own timeout/cancellation behavior.
Entry points, imports and tests
Console programs define global fn main() with no parameters and return void
or i64. An i64 process exit code must be 0..255. HTTP projects define apps and
do not define global main. A test-only project may omit both.
import "modules/file.rz"; loads a relative source module. Files merge into a
single global namespace; duplicate declarations and import cycles are errors.
Relative imports remain inside the project root. Aliased imports create namespaces with explicit public exports. Declared local dependencies use @name/path.rz and verified content locks. See modules and packages.
test arithmetic {
assert(1 + 1 == 2);
}
ryft test FILE.rz runs global tests in source/import order, reports each
PASS/FAIL, and returns nonzero on failures. Tests use pure global helpers;
app-private helpers, HTTP request state and storage are unavailable to them.
Execution counters reset per test. Process aborts and OOM cannot be caught.
Apps and HTTP routes
app api {
base "/api";
budget connections 32;
storage products: sqlite("products.db");
permit env "API_TOKEN";
fn greeting() -> String { return "hello"; }
get "/hello/{name}" -> Response {
return response(200, json({ "name": param("name"), "message": greeting() }));
}
}
App helpers are private; another app cannot call them by bare name or access
api.member. Global functions and structs are shared. Storage handles are
not first-class values and cannot escape the declaring app. env("NAME")
requires that exact literal name in permit env "NAME";.
Routes support get/post/put/patch/delete/head/options and return Json, String
or Response. Json and String default to status 200 with appropriate content
types. Response selects a 200..599 status. A base may occur once and defaults
to empty. Paths are absolute; base / is equivalent to empty. A non-root base
trims its trailing slash; route / under /api resolves to /api/.
Dynamic parameters use a full segment: /{id}. Parameter names are identifiers
and cannot repeat in one path. The same template used with different methods
must use identical parameter names. Static routes take precedence over dynamic
routes. Wildcards, fragments, query strings in declarations, whitespace,
backslashes, doubled slashes and dot segments are rejected. Query strings are
read with query; path parameters are decoded by the HTTP router. Duplicate
method/full-path declarations fail across the entire project.
budget connections N; admits at most N active concurrent requests for an app
(default 100). It is not a raw TCP connection count. budget memory N MB;
accepts B/KB/MB/GB with 1024-based multipliers, but remains metadata and emits a
warning in compilation because Tier 0 does not enforce it. With --isolate
on Windows, the declared budget becomes the worker’s Job Object committed-memory
cap; undeclared worker memory defaults to 256 MiB. This is not an RSS guarantee.
budget cpu N%; accepts 1..100 and configures a Windows Job Object hard rate
cap in isolated mode. Tier 0 treats it as metadata and warns. CPU rates apply
within the OS job hierarchy, not a guarantee of response time. Typed message
permissions remain unsupported.
Storage paths are relative to <data-dir>/<app>/; absolute paths, drive
prefixes, parent traversal and NUL are rejected. Runtime checks guard ordinary
path aliases. This is ownership validation, not hostile filesystem sandboxing.
Diagnostics and implementation limits
Errors include code, source path, line, column and a caret span. RZ0001/0002: lexing; RZ0100/0101: syntax/depth; RZ1001–1004: names/types/returns/boundaries; RZ2000–2002: routes/budgets; RZ3000–3002: unsupported operations/capabilities; RZ4001: imports/source limits; RZ9000: resource warnings. Analysis reports the first error, rather than recovering and reporting an entire batch.
Source nesting and expression depth are limited to 128; value struct composition to 128. Projects have limits of 1024 files, 128 import depth and 64 MiB of source. Those limits do not constitute an adversarial compiler resource sandbox. Source compatibility is not frozen before the 1.0 gates.