Ryft checks types before generating native code. There are no implicit conversions between strings, integers and booleans. Values have copy-like semantics: generated code clones owned strings, JSON and model values when necessary.
Structs and value methods
struct Task { title: String; complete: bool; }
impl Task {
fn finish(self: Task) -> Task {
let mut changed = self;
changed.complete = true;
return changed;
}
}
fn main() {
let task = Task { title: "Ship Ryft", complete: false };
let finished = task.finish();
assert(finished.complete);
assert(!task.complete);
}
Fields can be assigned through a mutable local. Methods consume a copy-like owned value; they do not borrow and mutate the caller’s original. References, pointers and mutable receiver borrowing are not exposed.
Enums and exhaustive matching
enum Status { todo, done }
enum Message { Text(String), Count(i64) }
fn describe(message: Message) -> String {
match message {
Message.Text(text) => { return text; },
Message.Count(number) => { return to_string(number); }
}
}
All variants must be handled once, with the correct payload binding. A variant can carry one value. Use a struct when a payload needs multiple fields. Unit enums support equality; payload enums are inspected with matching.
Result and Option
Result<T,E> has Ok(T) and Err(E). Option<T> has Some(T) and None. Handle them explicitly:
fn parse_count(text: String) -> i64 {
match parse_i64(text) {
Ok(value) => { return value; },
Err(message) => { return 0; }
}
}
test expected_errors {
assert(parse_count("42") == 42);
assert(parse_count("not a number") == 0);
let missing = Option<String>.None;
match missing {
Some(value) => { assert(false); },
None => { assert(true); }
}
}
Use Result<i64,String>.Err("reason") when constructing a result whose other type cannot be inferred. There is no ? propagation operator, exception syntax or implicit result unwrapping. Use fallible parsing for untrusted inputs; strict conversion builtins can fail the handler at runtime.
Typed collections
test collections {
let numbers: List<i64> = list_push(list(10, 20), 30);
match list_at(numbers, 2) {
Some(number) => { assert(number == 30); },
None => { assert(false); }
}
let counts = map_put(Map<String, i64>.new(), "tasks", 3);
match map_get(counts, "tasks") {
Some(count) => { assert(count == 3); },
None => { assert(false); }
}
}
List<T>.new() constructs an empty typed list. list(...) requires at least one homogeneous argument. List indexing is zero-based and returns Option<T>; missing keys do the same. Maps currently require String keys. list_push and map_put return updated values; keep the returned value. JSON arrays remain dynamically typed Json values and are distinct from List<T>.
Inferred generic functions
fn identity<T>(value: T) -> T { return value; }
fn singleton<T>(value: T) -> List<T> { return list(value); }
test inference {
assert(identity(42) == 42);
assert(identity("Ryft") == "Ryft");
assert(len(singleton(true)) == 1);
}
Call types are inferred from arguments. Repeated occurrences of one type parameter must agree. Generated generic values require cloning. User-defined generic structs, trait constraints and specialization are not implemented.
Strict JSON codecs
Task.from_json(value) returns Result<Task,String>. json(task) or task.to_json() encodes a serializable value. Primitive types, serializable structs/enums and supported containers can participate in codecs. Response values cannot be serialized as data.
Struct decoding rejects missing fields, unknown fields, incorrect types and invalid enum tags. An Option<T> field must still be present; use JSON null for None. Integer fields reject floating-point/exponent spellings. Unit enums encode as strings, such as "todo"; payload enums use {"variant":"Text","value":"hello"}. These codecs validate shape and type; domain rules such as nonempty titles belong in your application.
See the executable feature example and language reference for primitives and control flow.