Raven Language (v2)
Raven v2 is a statically-typed, ahead-of-time compiled language implemented in Rust. Source lowers through a resolver, type checker, HIR, and a monomorphizing MIR, then a Cranelift backend emits a native binary. Memory is managed by a tracing garbage collector. The syntax reads like a blend of Rust and Swift, with local type inference, no semicolons, traits, generics, sum types, and pattern matching. The single most useful thing this skill does is steer you away from the non-obvious pitfalls so the first version of your code parses, type-checks, and links.
v2 is a clean break from v1. If you find old material mentioning elseif, C-style for loops, int/string lowercase types, format("{}", x), import math;, or main(); at the bottom of a file, that is v1 and does not apply here.
When to read what
- This file: read every time. The pitfalls and templates cover most tasks.
references/builtins.md: the always-available functions and the methods on String, List, Map, Set.
references/stdlib.md: per-module API for std/io, std/string, std/collections, std/math, std/iter, std/fs, std/time, std/json, and more.
references/rvpm.md: starting a project, rv.toml, GitHub-direct packages, the raven and rvpm CLIs.
references/grammar.md: exact lexical and grammar rules when something parses surprisingly.
The stdlib source lives in stdlib/std/*.rv in the Raven repo and is written in Raven itself, so reading it is the authoritative answer for any signature.
Pitfalls to internalise
These are the mistakes that ruin first-try compilation. Burn them in.
- No semicolons. Statements end at the newline. Do not terminate lines with
;.
- Types are PascalCase.
Int, Float, Bool, String, Char, Unit. There is no int/string. Unit is the no-value type (the implicit return).
- Type annotations are optional. Local inference works:
let x = 5 infers Int. Annotate when you want to be explicit or when inference can't tell (let xs: List<Int> = []). Function parameters and return types are still written out.
let is mutable; there is no let mut. Reassign freely. Compound assignment works: += -= *= /= %=. const is for immutable compile-time constants and works both as a local (const X = 5) and at module level (const MAX: Int = 100).
- Logical operators are
&&, ||, !. The words and/or/not are NOT operators.
else if is two words (v1's elseif is gone). if is also an expression: let s = if x > 0 { "pos" } else { "neg" }.
for is range/iterator based. for i in 0..10 { } (exclusive), for i in 1..=10 { } (inclusive), for item in list { }. while cond { } and loop { } exist. break and continue work.
- String interpolation is
"${expr}". The expression can be arbitrary: a nested string literal ("${a.concat("y")}"), a macro call ("${square!(5)}"), and a struct value that derives ToString ("${p}") all interpolate directly.
String length is .length(), not .len(). .len() type-checks but fails in codegen. List/Map/Set use .len(). String methods (.to_upper(), .trim(), .split(), .concat(), …) require import std/string.
- No
null. Absence is Option<T> (sugar T?) with Some(x)/None. Fallible results are Result<T, E> with Ok(x)/Err(e) and the ? operator. Some, None, Ok, Err are in scope without imports.
- Enum variants are constructed qualified:
Shape.Circle(2.0), Color.Red. In match, the patterns are bare: Circle(r) -> .... match is exhaustive.
- No visibility modifiers. Every top-level
fun/struct/enum/trait is importable. There is no export and no pub.
fun main() is the entry point. Define it; do NOT call main() yourself. There is no top-level statement execution.
- Prefer selector imports for free functions.
import std/fs { write } then write(path, data) is the form that always works. Module-qualified access (import std/fs then fs.exists(...)) works for some runtime-backed modules but not all (import std/math then math.sqrt(...) fails), so reach for the selector form. Types (Map) and methods (.to_upper) come in through import std/collections { Map } / import std/string.
match on String literal patterns works. match s { "a" -> ..., _ -> ... } matches as expected, and strings support </> ordering.
- Reading module-level bindings works; mutating them does not. A top-level
let/const is readable from any function (let greeting: String = "hi"), but reassigning a module-level binding from a function mis-compiles (binop lhs used a Unit value). For mutable state, keep it inside functions, thread it through parameters, or hold it in a struct you pass around.
Anatomy of a Raven file
// imports: std modules by name, locals and packages by quoted path
import std/io { println }
import std/collections { Map }
import "./board" // local ./board.rv
import "github.com/user/raven-json" { parse } // GitHub-direct package
// a trait
trait Display {
fun show(self) -> String
}
// a struct + an impl of the trait + inherent methods
struct Point {
x: Float,
y: Float,
}
impl Display for Point {
fun show(self) -> String = "(${self.x}, ${self.y})"
}
impl Point {
fun magnitude(self) -> Float = sqrt(self.x * self.x + self.y * self.y)
}
import std/math { sqrt }
// a free function (single-expression body)
fun midpoint(a: Point, b: Point) -> Point =
Point { x: (a.x + b.x) / 2.0, y: (a.y + b.y) / 2.0 }
// entry point: defined, never called by you
fun main() {
let p = Point { x: 3.0, y: 4.0 }
println("mag = ${p.magnitude()}")
println(p.show())
}
Implicit rules:
- Imports:
import std/<module> { names } for the bundled stdlib; import "./rel" for a sibling file; import "github.com/user/repo" { names } for a package fetched by rvpm. Selectors bring the names into scope unqualified.
- No entry call: the runtime calls
main for you.
- Single-expression bodies:
fun f(...) -> T = <expr> is shorthand for { return <expr> }.
- Trailing commas are allowed in struct definitions/literals and collection literals.
Templates
Working code you can adapt. These compile.
Variables, control flow
fun main() {
let total = 0
for i in 1..=10 {
if i % 2 == 0 {
total += i
}
}
let label = if total > 20 { "big" } else { "small" }
print("${total} is ${label}")
}
Collections and iterators
import std/io { println }
import std/collections { Map }
import std/iter { collect, fold }
fun main() {
let nums = [1, 2, 3, 4, 5, 6]
println("len = ${nums.len()}, first = ${nums[0]}")
// lazy pipeline: consume with collect / fold / count
let doubledEvens: List<Int> =
collect(nums.iter().filter(fun(x: Int) -> Bool = x % 2 == 0).map(fun(x: Int) -> Int = x * 2))
println("count = ${doubledEvens.len()}")
let sum = fold(nums.iter(), 0, fun(acc: Int, v: Int) -> Int = acc + v)
println("sum = ${sum}")
let tally: Map<String, Int> = Map.new()
tally.set("a", 1)
match tally.get("a") {
Some(n) -> println("a = ${n}"),
None -> println("missing"),
}
}
Struct, trait, dynamic dispatch
trait Shape {
fun area(self) -> Float
}
struct Circle { r: Float }
struct Square { side: Float }
impl Shape for Circle {
fun area(self) -> Float = 3.14159 * self.r * self.r
}
impl Shape for Square {
fun area(self) -> Float = self.side * self.side
}
fun describe(s: dyn Shape) {
print("area = ${s.area()}")
}
fun main() {
describe(Circle { r: 2.0 })
describe(Square { side: 3.0 })
}
Note: List<dyn Trait> (a heterogeneous list of trait objects) is not supported in this release. Pass trait objects to dyn-typed parameters or assign to a dyn-typed local.
Enums, match, Option, Result, ?
enum Shape {
Circle(Float)
Rectangle(Float, Float)
}
fun area(s: Shape) -> Float =
match s {
Circle(r) -> 3.14159 * r * r,
Rectangle(w, h) -> w * h,
}
fun checked_div(a: Int, b: Int) -> Result<Int, String> {
if b == 0 {
return Err("divide by zero")
}
return Ok(a / b)
}
fun halve_then_div(a: Int, b: Int) -> Result<Int, String> {
let q = checked_div(a, b)? // ? propagates Err, unwraps Ok
return Ok(q / 2)
}
fun main() {
print("${area(Shape.Circle(2.0))}")
match halve_then_div(20, 5) {
Ok(v) -> print("ok ${v}"),
Err(e) -> print("err ${e}"),
}
}
Generics with a trait bound
trait Describe {
fun describe(self) -> String
}
struct Dog {}
impl Describe for Dog {
fun describe(self) -> String = "a dog"
}
fun announce<T: Describe>(x: T) {
print("this is ${x.describe()}")
}
fun main() {
announce(Dog {})
}
defer (LIFO, runs at function exit)
fun main() {
let log = [1]
defer log.push(3) // runs second
defer log.push(2) // runs first
print("len now ${log.len()}")
}
Concurrency: spawn + channels
import std/sync { channel, channel_buffered, yield_now }
fun main() {
let ch = channel()
spawn(fun() -> Unit {
let i = 1
while i <= 5 {
ch.send(i)
i = i + 1
}
})
let sum = 0
let n = 0
while n < 5 {
sum += ch.recv()
n += 1
}
print("sum = ${sum}") // 15
}
Goroutines run in parallel on a pool of worker threads (one per core), so spawn is true parallelism, not cooperative time-slicing. A goroutine only ever suspends at a blocking point (a full/empty channel, yield_now, sleep_millis), and the scheduler may resume it on a different worker than it ran on before. Channels carry Int and block when full (send) or empty (recv).
std/sync also has a Mutex, a WaitGroup, and select:
import std/sync { mutex, wait_group, channel, select_recv }
fun main() {
// Mutex: lock() blocks until free, unlock() releases (call only while held).
let m = mutex()
m.lock()
m.unlock()
// WaitGroup: add() before spawning, done() as each finishes, wait() blocks to zero.
let wg = wait_group()
wg.add(1)
spawn(fun() -> Unit {
wg.done()
})
wg.wait()
// select_recv: block on several channels, lowest ready index wins. The
// SelectResult is { index, value } (index -1 if the list was empty).
let a = channel()
let b = channel()
spawn(fun() -> Unit { a.send(7) })
let r = select_recv([a, b])
print("chan ${r.index} -> ${r.value}")
}
Channels, wait groups, and select sets hold runtime registry entries with no destructor; call .free() (or select_recv frees its own set) when you are done with one to avoid leaking the entry.
Metaprogramming: derive + macros + reflection
@derive(Eq, Hash, ToString, Debug)
struct Point { x: Int, y: Int }
macro square { ($x:expr) => { ($x) * ($x) } }
fun main() {
let p = Point { x: 1, y: 2 }
print(p.to_string()) // Point { x: 1, y: 2 }
print("${square!(5)} fields=${field_names<Point>().len()}")
}
Derivable traits: Eq, Hash, ToString, Debug, and Ord. @derive(Ord) adds compare(self, other) -> Int (negative / zero / positive), comparing structs field-by-field in declaration order and enums by variant order then payload. Pair it with import std/cmp { sort } to sort a List:
import std/cmp { sort }
@derive(Ord)
struct Version { major: Int, minor: Int }
fun main() {
let vs = sort([Version { major: 1, minor: 2 }, Version { major: 1, minor: 0 }])
print("${vs[0].minor}") // 0
}
C FFI
import std/ffi { alloc, free, load, store }
extern "C" {
fun abs(x: CInt) -> CInt
fun strlen(s: CStr) -> CSize
}
fun main() {
print("${abs(-7)}")
let len = strlen(c"hello") // c"..." is a C string literal
print("${len}")
let buf = alloc<CInt>(2)
store<CInt>(buf, 42)
print("${load<CInt>(buf)}")
free<CInt>(buf)
}
Built-in cheatsheet
Always available, no import. See references/builtins.md for details.
| Need |
Use |
| Print a line |
print("...") (interpolate with ${expr}) |
| Print via stdlib |
println(...) after import std/io { println } |
| List length / element |
xs.len(), xs[i], xs.push(v) |
| Iterator from a list |
xs.iter() then .map/.filter, consumed by std/iter |
| Option / Result values |
Some(x), None, Ok(x), Err(e) |
| Goroutine |
spawn(fun() -> Unit { ... }) |
| Compile-time reflection |
type_name<T>(), field_names<T>() |
| Raw memory byte |
__str_byte_at(s, i) (low-level; prefer std/string) |
Stdlib at a glance
Bundled into the compiler; import with import std/<module> { names }. See references/stdlib.md.
| Module |
What's there |
std/io |
print, println |
std/string |
merges String methods: to_upper, to_lower, trim, split, concat, contains, replace, substring, length, … |
std/fmt |
string formatting: pad_left, pad_right, center, repeat, join, to_hex/to_binary/to_octal/to_radix, from_hex/from_radix, format_float, pad_int |
std/collections |
Map<K, V>, Set<T> with constructors and methods |
std/iter |
collect, fold, count over .iter().map(...).filter(...) pipelines |
std/math |
sqrt, pow, pow_int, abs, min, max, pi, e, trig, … |
std/fs |
read, write, append, exists, remove_file, list_dir, split_lines (all return Result where fallible) |
std/time |
now, now_millis, format_timestamp, parse_timestamp |
std/json |
JsonValue enum, parse, stringify |
std/sync |
channel, channel_buffered, send/recv, yield_now, sleep_millis, Mutex (mutex, lock/unlock), WaitGroup (wait_group, add/done/wait), select_recv |
std/ffi |
alloc, free, load, store, offset, is_null, null_ptr |
std/random |
Rng (new, from_entropy, next_int, gen_range) |
std/cmp |
min, max, clamp, sort, sorted_by (pairs with @derive(Ord)) |
std/http |
get, post, put, delete, patch, request; serve_connection for a basic server |
std/net |
connect, listen, dns_lookup, reachable |
std/tls |
client TLS: connect(addr, server_name) (verified), connect_with + config() builder (add_ca_file, client_cert, insecure_skip_verify), upgrade(tcp_stream, server_name) for STARTTLS (Postgres/MySQL); TlsStream read/write/close. std/http already does https:// |
std/encoding |
hex_encode, hex_decode, url_encode, base64 helpers |
std/env |
get_env, has_env, get_env_or, args, arg_count, arg_at, exit, os_name, arch |
std/hash |
fnv, djb, crc, checksum, combine |
std/path |
join, basename, dirname, extension, stem, normalize, is_absolute |
std/process |
run, run_with_input |
std/regex |
compile and match against compiled patterns |
std/test |
assertions for rvpm test: assert, assert_msg, assert_true/assert_false, assert_eq/assert_ne (generic), assert_eq_int/assert_eq_str/assert_eq_float, assert_some/assert_none/assert_ok |
If a name isn't here, read stdlib/std/<module>.rv in the Raven repo. Note: a dependency package can also import std free functions (fixed in 2.0.2).
Running and project layout
my_project/
├── rv.toml # [package] / [dependencies] / optional [ffi], [fmt]
└── src/
└── main.rv # entry point — defines fun main(), not called manually
rvpm init my_project # scaffold in the current dir (--lib for a library, lib.rv)
rvpm new my_project # scaffold in a fresh my_project/ dir (--lib)
rvpm run # build src/main.rv and run it (forwards args: rvpm run -- a b)
rvpm build # compile to target/raven-out/<name>, or type-check a lib.rv
rvpm test # run fun test_*() tests in *_test.rv files (assert with import std/test)
rvpm doc # generate Markdown API docs into target/doc
rvpm fmt # format .rv files in place (pass paths for a library: rvpm fmt lib.rv)
rvpm fmt --check # CI: non-zero if anything would change
rvpm add github.com/user/repo@v1.0.0 # add a GitHub-direct dependency, write rv.lock
rvpm install # resolve rv.toml against rv.lock and fill the cache
rvpm update [pkg] # re-resolve and rewrite rv.lock for one package or all
rvpm fetch github.com/user/repo@v1.0.0 # fetch a package into the shared cache
rvpm lock # generate or validate rv.lock
rvpm cache list # inspect the shared cache (also dir / clean)
rvpm version # print the rvpm version (also --version, -V)
raven build file.rv -o out # compile a single file to a native binary
./out # run it
There is no raven file.rv direct-run, no -c type-check flag, and no REPL in v2; compiling is the check. A C linker must be on PATH (link.exe on Windows, cc/clang on Unix) so the compiler can link the runtime into your program.
Recommended workflow
- Sketch types first: structs, enums, traits.
- Write small free functions; group methods in
impl blocks.
- Write
fun main() (do not call it).
raven build src/main.rv -o app (or rvpm run). Compile early and often; the type checker catches the bulk of mistakes.
- If a parse error appears, the usual cause is a pitfall above: a stray
;, lowercase type name, .len() on a String, or an unqualified enum constructor.
rvpm fmt before committing.
Style
- 4-space indent, no semicolons.
- Order: imports → traits → structs/enums → impls → free functions →
fun main.
snake_case for functions and variables, PascalCase for types and enum variants.
- Prefer single-expression bodies (
fun f() -> T = expr) for one-liners.
- Use string interpolation (
"${x}") instead of manual concatenation.
- Keep
match arms exhaustive; add a _ -> ... arm only when a catch-all is truly intended.
1---2name: raven-language-skill3description: Reference and patterns for writing Raven programming language source (.rv files). Use this whenever the user is writing, editing, debugging, or reviewing Raven code, working in an rvpm project (rv.toml), discussing Raven syntax, importing from Raven's stdlib (std/io, std/string, std/fmt, std/collections, std/math, std/iter, std/fs, std/time, std/json, std/net, std/http, std/tls, std/ffi, std/sync, std/cmp, std/random, std/env, std/encoding, std/hash, std/path, std/process, std/regex), or running raven/rvpm commands. Raven v2 is a statically-typed compiled language (Cranelift backend, tracing GC) with generics, traits, sum types, pattern matching, concurrency, a C FFI, and metaprogramming. It has a few syntax pitfalls (no semicolons, PascalCase types, string-method name is `.length()` not `.len()`, enum construction is qualified) that this skill helps Claude avoid. Trigger even when the user just mentions a `.rv` file or rvpm. Do NOT trigger for the Raven compiler source itself (the `.rs` files) or unrelated l4---56# Raven Language (v2)78Raven v2 is a statically-typed, ahead-of-time compiled language implemented in Rust. Source lowers through a resolver, type checker, HIR, and a monomorphizing MIR, then a Cranelift backend emits a native binary. Memory is managed by a tracing garbage collector. The syntax reads like a blend of Rust and Swift, with local type inference, no semicolons, traits, generics, sum types, and pattern matching. The single most useful thing this skill does is steer you away from the non-obvious pitfalls so the first version of your code parses, type-checks, and links.910> v2 is a clean break from v1. If you find old material mentioning `elseif`, C-style `for` loops, `int`/`string` lowercase types, `format("{}", x)`, `import math;`, or `main();` at the bottom of a file, that is v1 and does not apply here.1112## When to read what1314- **This file**: read every time. The pitfalls and templates cover most tasks.15- **`references/builtins.md`**: the always-available functions and the methods on `String`, `List`, `Map`, `Set`.16- **`references/stdlib.md`**: per-module API for `std/io`, `std/string`, `std/collections`, `std/math`, `std/iter`, `std/fs`, `std/time`, `std/json`, and more.17- **`references/rvpm.md`**: starting a project, `rv.toml`, GitHub-direct packages, the `raven` and `rvpm` CLIs.18- **`references/grammar.md`**: exact lexical and grammar rules when something parses surprisingly.1920The stdlib source lives in `stdlib/std/*.rv` in the Raven repo and is written in Raven itself, so reading it is the authoritative answer for any signature.2122## Pitfalls to internalise2324These are the mistakes that ruin first-try compilation. Burn them in.25261. **No semicolons.** Statements end at the newline. Do not terminate lines with `;`.272. **Types are PascalCase.** `Int`, `Float`, `Bool`, `String`, `Char`, `Unit`. There is no `int`/`string`. `Unit` is the no-value type (the implicit return).283. **Type annotations are optional.** Local inference works: `let x = 5` infers `Int`. Annotate when you want to be explicit or when inference can't tell (`let xs: List<Int> = []`). Function parameters and return types are still written out.294. **`let` is mutable; there is no `let mut`.** Reassign freely. Compound assignment works: `+= -= *= /= %=`. `const` is for immutable compile-time constants and works both as a local (`const X = 5`) and at module level (`const MAX: Int = 100`).305. **Logical operators are `&&`, `||`, `!`.** The words `and`/`or`/`not` are NOT operators.316. **`else if` is two words** (v1's `elseif` is gone). `if` is also an expression: `let s = if x > 0 { "pos" } else { "neg" }`.327. **`for` is range/iterator based.** `for i in 0..10 { }` (exclusive), `for i in 1..=10 { }` (inclusive), `for item in list { }`. `while cond { }` and `loop { }` exist. `break` and `continue` work.338. **String interpolation is `"${expr}"`.** The expression can be arbitrary: a nested string literal (`"${a.concat("y")}"`), a macro call (`"${square!(5)}"`), and a struct value that derives `ToString` (`"${p}"`) all interpolate directly.349. **`String` length is `.length()`, not `.len()`.** `.len()` type-checks but fails in codegen. `List`/`Map`/`Set` use `.len()`. String methods (`.to_upper()`, `.trim()`, `.split()`, `.concat()`, …) require `import std/string`.3510. **No `null`.** Absence is `Option<T>` (sugar `T?`) with `Some(x)`/`None`. Fallible results are `Result<T, E>` with `Ok(x)`/`Err(e)` and the `?` operator. `Some`, `None`, `Ok`, `Err` are in scope without imports.3611. **Enum variants are constructed qualified:** `Shape.Circle(2.0)`, `Color.Red`. In `match`, the patterns are bare: `Circle(r) -> ...`. `match` is exhaustive.3712. **No visibility modifiers.** Every top-level `fun`/`struct`/`enum`/`trait` is importable. There is no `export` and no `pub`.3813. **`fun main()` is the entry point.** Define it; do NOT call `main()` yourself. There is no top-level statement execution.3914. **Prefer selector imports for free functions.** `import std/fs { write }` then `write(path, data)` is the form that always works. Module-qualified access (`import std/fs` then `fs.exists(...)`) works for some runtime-backed modules but not all (`import std/math` then `math.sqrt(...)` fails), so reach for the selector form. Types (`Map`) and methods (`.to_upper`) come in through `import std/collections { Map }` / `import std/string`.4015. **`match` on `String` literal patterns works.** `match s { "a" -> ..., _ -> ... }` matches as expected, and strings support `<`/`>` ordering.4116. **Reading module-level bindings works; mutating them does not.** A top-level `let`/`const` is readable from any function (`let greeting: String = "hi"`), but reassigning a module-level binding from a function mis-compiles (`binop lhs used a Unit value`). For mutable state, keep it inside functions, thread it through parameters, or hold it in a struct you pass around.4243## Anatomy of a Raven file4445```raven46// imports: std modules by name, locals and packages by quoted path47import std/io { println }48import std/collections { Map }49import "./board" // local ./board.rv50import "github.com/user/raven-json" { parse } // GitHub-direct package5152// a trait53trait Display {54 fun show(self) -> String55}5657// a struct + an impl of the trait + inherent methods58struct Point {59 x: Float,60 y: Float,61}6263impl Display for Point {64 fun show(self) -> String = "(${self.x}, ${self.y})"65}6667impl Point {68 fun magnitude(self) -> Float = sqrt(self.x * self.x + self.y * self.y)69}7071import std/math { sqrt }7273// a free function (single-expression body)74fun midpoint(a: Point, b: Point) -> Point =75 Point { x: (a.x + b.x) / 2.0, y: (a.y + b.y) / 2.0 }7677// entry point: defined, never called by you78fun main() {79 let p = Point { x: 3.0, y: 4.0 }80 println("mag = ${p.magnitude()}")81 println(p.show())82}83```8485Implicit rules:8687- **Imports**: `import std/<module> { names }` for the bundled stdlib; `import "./rel"` for a sibling file; `import "github.com/user/repo" { names }` for a package fetched by rvpm. Selectors bring the names into scope unqualified.88- **No entry call**: the runtime calls `main` for you.89- **Single-expression bodies**: `fun f(...) -> T = <expr>` is shorthand for `{ return <expr> }`.90- **Trailing commas** are allowed in struct definitions/literals and collection literals.9192## Templates9394Working code you can adapt. These compile.9596### Variables, control flow9798```raven99fun main() {100 let total = 0101 for i in 1..=10 {102 if i % 2 == 0 {103 total += i104 }105 }106 let label = if total > 20 { "big" } else { "small" }107 print("${total} is ${label}")108}109```110111### Collections and iterators112113```raven114import std/io { println }115import std/collections { Map }116import std/iter { collect, fold }117118fun main() {119 let nums = [1, 2, 3, 4, 5, 6]120 println("len = ${nums.len()}, first = ${nums[0]}")121122 // lazy pipeline: consume with collect / fold / count123 let doubledEvens: List<Int> =124 collect(nums.iter().filter(fun(x: Int) -> Bool = x % 2 == 0).map(fun(x: Int) -> Int = x * 2))125 println("count = ${doubledEvens.len()}")126127 let sum = fold(nums.iter(), 0, fun(acc: Int, v: Int) -> Int = acc + v)128 println("sum = ${sum}")129130 let tally: Map<String, Int> = Map.new()131 tally.set("a", 1)132 match tally.get("a") {133 Some(n) -> println("a = ${n}"),134 None -> println("missing"),135 }136}137```138139### Struct, trait, dynamic dispatch140141```raven142trait Shape {143 fun area(self) -> Float144}145146struct Circle { r: Float }147struct Square { side: Float }148149impl Shape for Circle {150 fun area(self) -> Float = 3.14159 * self.r * self.r151}152impl Shape for Square {153 fun area(self) -> Float = self.side * self.side154}155156fun describe(s: dyn Shape) {157 print("area = ${s.area()}")158}159160fun main() {161 describe(Circle { r: 2.0 })162 describe(Square { side: 3.0 })163}164```165166Note: `List<dyn Trait>` (a heterogeneous list of trait objects) is not supported in this release. Pass trait objects to `dyn`-typed parameters or assign to a `dyn`-typed local.167168### Enums, match, Option, Result, ?169170```raven171enum Shape {172 Circle(Float)173 Rectangle(Float, Float)174}175176fun area(s: Shape) -> Float =177 match s {178 Circle(r) -> 3.14159 * r * r,179 Rectangle(w, h) -> w * h,180 }181182fun checked_div(a: Int, b: Int) -> Result<Int, String> {183 if b == 0 {184 return Err("divide by zero")185 }186 return Ok(a / b)187}188189fun halve_then_div(a: Int, b: Int) -> Result<Int, String> {190 let q = checked_div(a, b)? // ? propagates Err, unwraps Ok191 return Ok(q / 2)192}193194fun main() {195 print("${area(Shape.Circle(2.0))}")196 match halve_then_div(20, 5) {197 Ok(v) -> print("ok ${v}"),198 Err(e) -> print("err ${e}"),199 }200}201```202203### Generics with a trait bound204205```raven206trait Describe {207 fun describe(self) -> String208}209210struct Dog {}211impl Describe for Dog {212 fun describe(self) -> String = "a dog"213}214215fun announce<T: Describe>(x: T) {216 print("this is ${x.describe()}")217}218219fun main() {220 announce(Dog {})221}222```223224### defer (LIFO, runs at function exit)225226```raven227fun main() {228 let log = [1]229 defer log.push(3) // runs second230 defer log.push(2) // runs first231 print("len now ${log.len()}")232}233```234235### Concurrency: spawn + channels236237```raven238import std/sync { channel, channel_buffered, yield_now }239240fun main() {241 let ch = channel()242 spawn(fun() -> Unit {243 let i = 1244 while i <= 5 {245 ch.send(i)246 i = i + 1247 }248 })249 let sum = 0250 let n = 0251 while n < 5 {252 sum += ch.recv()253 n += 1254 }255 print("sum = ${sum}") // 15256}257```258259Goroutines run in **parallel** on a pool of worker threads (one per core), so `spawn` is true parallelism, not cooperative time-slicing. A goroutine only ever suspends at a blocking point (a full/empty channel, `yield_now`, `sleep_millis`), and the scheduler may resume it on a different worker than it ran on before. Channels carry `Int` and block when full (send) or empty (recv).260261`std/sync` also has a `Mutex`, a `WaitGroup`, and `select`:262263```raven264import std/sync { mutex, wait_group, channel, select_recv }265266fun main() {267 // Mutex: lock() blocks until free, unlock() releases (call only while held).268 let m = mutex()269 m.lock()270 m.unlock()271272 // WaitGroup: add() before spawning, done() as each finishes, wait() blocks to zero.273 let wg = wait_group()274 wg.add(1)275 spawn(fun() -> Unit {276 wg.done()277 })278 wg.wait()279280 // select_recv: block on several channels, lowest ready index wins. The281 // SelectResult is { index, value } (index -1 if the list was empty).282 let a = channel()283 let b = channel()284 spawn(fun() -> Unit { a.send(7) })285 let r = select_recv([a, b])286 print("chan ${r.index} -> ${r.value}")287}288```289290Channels, wait groups, and select sets hold runtime registry entries with no destructor; call `.free()` (or `select_recv` frees its own set) when you are done with one to avoid leaking the entry.291292### Metaprogramming: derive + macros + reflection293294```raven295@derive(Eq, Hash, ToString, Debug)296struct Point { x: Int, y: Int }297298macro square { ($x:expr) => { ($x) * ($x) } }299300fun main() {301 let p = Point { x: 1, y: 2 }302 print(p.to_string()) // Point { x: 1, y: 2 }303 print("${square!(5)} fields=${field_names<Point>().len()}")304}305```306307Derivable traits: `Eq`, `Hash`, `ToString`, `Debug`, and `Ord`. `@derive(Ord)` adds `compare(self, other) -> Int` (negative / zero / positive), comparing structs field-by-field in declaration order and enums by variant order then payload. Pair it with `import std/cmp { sort }` to sort a `List`:308309```raven310import std/cmp { sort }311312@derive(Ord)313struct Version { major: Int, minor: Int }314315fun main() {316 let vs = sort([Version { major: 1, minor: 2 }, Version { major: 1, minor: 0 }])317 print("${vs[0].minor}") // 0318}319```320321### C FFI322323```raven324import std/ffi { alloc, free, load, store }325326extern "C" {327 fun abs(x: CInt) -> CInt328 fun strlen(s: CStr) -> CSize329}330331fun main() {332 print("${abs(-7)}")333 let len = strlen(c"hello") // c"..." is a C string literal334 print("${len}")335 let buf = alloc<CInt>(2)336 store<CInt>(buf, 42)337 print("${load<CInt>(buf)}")338 free<CInt>(buf)339}340```341342## Built-in cheatsheet343344Always available, no import. See `references/builtins.md` for details.345346| Need | Use |347| --------------------------- | ---------------------------------------------------- |348| Print a line | `print("...")` (interpolate with `${expr}`) |349| Print via stdlib | `println(...)` after `import std/io { println }` |350| List length / element | `xs.len()`, `xs[i]`, `xs.push(v)` |351| Iterator from a list | `xs.iter()` then `.map`/`.filter`, consumed by `std/iter` |352| Option / Result values | `Some(x)`, `None`, `Ok(x)`, `Err(e)` |353| Goroutine | `spawn(fun() -> Unit { ... })` |354| Compile-time reflection | `type_name<T>()`, `field_names<T>()` |355| Raw memory byte | `__str_byte_at(s, i)` (low-level; prefer `std/string`) |356357## Stdlib at a glance358359Bundled into the compiler; import with `import std/<module> { names }`. See `references/stdlib.md`.360361| Module | What's there |362| ----------------- | ----------------------------------------------------------------------- |363| `std/io` | `print`, `println` |364| `std/string` | merges `String` methods: `to_upper`, `to_lower`, `trim`, `split`, `concat`, `contains`, `replace`, `substring`, `length`, … |365| `std/fmt` | string formatting: `pad_left`, `pad_right`, `center`, `repeat`, `join`, `to_hex`/`to_binary`/`to_octal`/`to_radix`, `from_hex`/`from_radix`, `format_float`, `pad_int` |366| `std/collections` | `Map<K, V>`, `Set<T>` with constructors and methods |367| `std/iter` | `collect`, `fold`, `count` over `.iter().map(...).filter(...)` pipelines |368| `std/math` | `sqrt`, `pow`, `pow_int`, `abs`, `min`, `max`, `pi`, `e`, trig, … |369| `std/fs` | `read`, `write`, `append`, `exists`, `remove_file`, `list_dir`, `split_lines` (all return `Result` where fallible) |370| `std/time` | `now`, `now_millis`, `format_timestamp`, `parse_timestamp` |371| `std/json` | `JsonValue` enum, `parse`, `stringify` |372| `std/sync` | `channel`, `channel_buffered`, `send`/`recv`, `yield_now`, `sleep_millis`, `Mutex` (`mutex`, `lock`/`unlock`), `WaitGroup` (`wait_group`, `add`/`done`/`wait`), `select_recv` |373| `std/ffi` | `alloc`, `free`, `load`, `store`, `offset`, `is_null`, `null_ptr` |374| `std/random` | `Rng` (`new`, `from_entropy`, `next_int`, `gen_range`) |375| `std/cmp` | `min`, `max`, `clamp`, `sort`, `sorted_by` (pairs with `@derive(Ord)`) |376| `std/http` | `get`, `post`, `put`, `delete`, `patch`, `request`; `serve_connection` for a basic server |377| `std/net` | `connect`, `listen`, `dns_lookup`, `reachable` |378| `std/tls` | client TLS: `connect(addr, server_name)` (verified), `connect_with` + `config()` builder (`add_ca_file`, `client_cert`, `insecure_skip_verify`), `upgrade(tcp_stream, server_name)` for STARTTLS (Postgres/MySQL); `TlsStream` read/write/close. `std/http` already does `https://` |379| `std/encoding` | `hex_encode`, `hex_decode`, `url_encode`, base64 helpers |380| `std/env` | `get_env`, `has_env`, `get_env_or`, `args`, `arg_count`, `arg_at`, `exit`, `os_name`, `arch` |381| `std/hash` | `fnv`, `djb`, `crc`, `checksum`, `combine` |382| `std/path` | `join`, `basename`, `dirname`, `extension`, `stem`, `normalize`, `is_absolute` |383| `std/process` | `run`, `run_with_input` |384| `std/regex` | `compile` and match against compiled patterns |385| `std/test` | assertions for `rvpm test`: `assert`, `assert_msg`, `assert_true`/`assert_false`, `assert_eq`/`assert_ne` (generic), `assert_eq_int`/`assert_eq_str`/`assert_eq_float`, `assert_some`/`assert_none`/`assert_ok` |386387If a name isn't here, read `stdlib/std/<module>.rv` in the Raven repo. Note: a dependency package can also import std free functions (fixed in 2.0.2).388389## Running and project layout390391```392my_project/393├── rv.toml # [package] / [dependencies] / optional [ffi], [fmt]394└── src/395 └── main.rv # entry point — defines fun main(), not called manually396```397398```bash399rvpm init my_project # scaffold in the current dir (--lib for a library, lib.rv)400rvpm new my_project # scaffold in a fresh my_project/ dir (--lib)401rvpm run # build src/main.rv and run it (forwards args: rvpm run -- a b)402rvpm build # compile to target/raven-out/<name>, or type-check a lib.rv403rvpm test # run fun test_*() tests in *_test.rv files (assert with import std/test)404rvpm doc # generate Markdown API docs into target/doc405rvpm fmt # format .rv files in place (pass paths for a library: rvpm fmt lib.rv)406rvpm fmt --check # CI: non-zero if anything would change407408rvpm add github.com/user/repo@v1.0.0 # add a GitHub-direct dependency, write rv.lock409rvpm install # resolve rv.toml against rv.lock and fill the cache410rvpm update [pkg] # re-resolve and rewrite rv.lock for one package or all411rvpm fetch github.com/user/repo@v1.0.0 # fetch a package into the shared cache412rvpm lock # generate or validate rv.lock413rvpm cache list # inspect the shared cache (also dir / clean)414rvpm version # print the rvpm version (also --version, -V)415416raven build file.rv -o out # compile a single file to a native binary417./out # run it418```419420There is no `raven file.rv` direct-run, no `-c` type-check flag, and no REPL in v2; compiling is the check. A C linker must be on PATH (`link.exe` on Windows, `cc`/`clang` on Unix) so the compiler can link the runtime into your program.421422## Recommended workflow4234241. Sketch types first: structs, enums, traits.4252. Write small free functions; group methods in `impl` blocks.4263. Write `fun main()` (do not call it).4274. `raven build src/main.rv -o app` (or `rvpm run`). Compile early and often; the type checker catches the bulk of mistakes.4285. If a parse error appears, the usual cause is a pitfall above: a stray `;`, lowercase type name, `.len()` on a `String`, or an unqualified enum constructor.4296. `rvpm fmt` before committing.430431## Style432433- 4-space indent, no semicolons.434- Order: imports → traits → structs/enums → impls → free functions → `fun main`.435- `snake_case` for functions and variables, `PascalCase` for types and enum variants.436- Prefer single-expression bodies (`fun f() -> T = expr`) for one-liners.437- Use string interpolation (`"${x}"`) instead of manual concatenation.438- Keep `match` arms exhaustive; add a `_ -> ...` arm only when a catch-all is truly intended.