Reef Language Specification
A Modern Concurrent Systems Language
Last reviewed on version: 0.9.0 Status: Design Specification (mostly implemented — see markers)
Aligned with the numbered reference/ pages; where this document summarizes, the reference pages govern.
Changes from v0.2:
- Added inline assembly support (
asm fn/asm proc) - Added baremetal compilation targets
- Multi-architecture support (AMD64, ARM64)
Recent Updates (v0.3.2 - February 22, 2026):
- ✅ Array methods:
append,pop,remove(immutable-style, return new arrays) - ✅ Active Objects codegen fix: Methods use direct lock/unlock instead of thread-per-call
- ✅ Main process registration:
reef_objects_register_main_process()enables AO method calls from main - ✅ Implementation status annotations: Summary table and inline status markers for unimplemented features
- ⚠️ Thread-safety warning: Module-level
mutdocumented as unsafe for concurrent Active Object access
Recent Updates (v0.3.1 - December 8, 2025):
- ✅ Module-Level Scope:
const,let,mutdeclarations at module level - ✅ Pure Reef PRNG: math/random uses Xorshift32 (no libc dependency)
- See Section 9.5 for complete module-level scope documentation
Recent Updates (v0.3.0 - December 6, 2025):
- ✅ Inline Assembly:
asm fnandasm procfor low-level code - ✅ Baremetal Compilation:
--target amd64-baremetal,--target arm64-baremetal - ✅ Multi-Architecture: Support for AMD64 (Intel syntax) and ARM64
- ✅ Kernel Development: Custom entry points, linker scripts
- See Section 17 for complete inline assembly documentation
Recent Updates (v0.2.7 - November 29, 2025):
- ✅ Full closure implementation complete (7 phases)
- ✅ Lambda expressions:
fn(x: int): int => x * 2 - ✅ Block-body lambdas:
fn(x: int): int ... end fn - ✅ Procedure lambdas:
proc(x: int) => println(x) - ✅ Variable capture (immutable and mutable)
- ✅ Function types:
Fn[int, int]for HOF parameters - ✅ Escape analysis optimization (stack allocation for non-escaping closures)
Recent Updates (v0.2.5 - November 28, 2025):
- ✅
bytetype alias foruint8(useful for FFI byte arrays) - ✅ Pointer ↔ array type coercions in
unsafeblocks - ✅ Integer narrowing coercions in
unsafeblocks (int→uint8, etc.)
Recent Updates (v0.2.4 - November 28, 2025):
- ✅ Array/string slicing syntax:
arr[start..end],arr[start..],arr[..end] - ✅ Multi-line strings with triple quotes (
""")
Recent Updates (v0.2.3 - November 28, 2025):
- ✅ Default parameters for functions and procedures
- ✅ Match guards with
whenclause
Recent Updates (v0.2.2 - November 27, 2025):
- ✅ Generic type inference for constructors and function calls
Earlier Updates (v0.2):
- Distinction between
proc(no return) andfn(returns value) - Changed
vartomutfor mutable variables - Changed
thistoselffor object self-reference - Added
unlesskeyword for negative conditionals - String interpolation with
"${expression}"syntax nilrestricted tounsafeblocks only (useOption[T]in safe code)runis a keyword for Active Objects- Raw strings (
r"...") have no escapes or interpolation spawnis inline only (no block syntax)
Table of Contents
- Introduction
- Design Philosophy
- Lexical Structure
- Types and Values
- Expressions and Operators
- Statements and Control Flow
- Procedures and Functions
- Active Objects
- Modules and Packages
- Generics
- Error Handling and Concurrency Primitives
- Memory Management
- Unsafe Code
- Documentation Generation
- Standard Library Overview
- Interoperability
- Inline Assembly
- Appendix: Grammar Summary
Implementation Status Summary
| Feature | Status |
|---|---|
Slice types []T |
Not Implemented |
Array methods (append, pop, remove) |
Implemented (immutable-style, return new arrays) |
for item in collection |
Implemented (arrays, strings, index variant, step, range loops) |
Variadic parameters ...T |
Not Implemented |
| Trait method dispatch | Implemented (static dispatch via monomorphization) |
Expression interpolation ${expr} |
Implemented (variables and expressions) |
Object inheritance extends |
Implemented (0.9 — passive objects, single inheritance, virtual methods; see 040_OBJECTS.md) |
Conditional imports ifarch |
Implemented (ifarch <arch> ... end ifarch blocks) |
| Process management (fork/exec/setsid) | Implemented (v0.4.0 - sys.process module) |
| File descriptor operations | Implemented (v0.4.0 - sys.fd module) |
| Signal enhancements (block/wait/self-pipe) | Implemented (v0.4.0 - sys.signal module) |
| Unix domain sockets | Implemented (v0.4.0 - net.unix module) |
| Event loop / poll(2) | Implemented (v0.4.0 - sys.poll module) |
| GC configuration | Implemented (v0.4.0 - --no-gc flag) |
| Filesystem stat/metadata | Implemented (v0.5.0 - fs.stat module) |
| Filesystem permissions | Implemented (v0.5.0 - fs.perm module) |
| Filesystem links | Implemented (v0.5.0 - fs.link module) |
| Filesystem copy/remove/rename | Implemented (v0.5.0 - fs.ops module) |
1. Introduction
1.1 Purpose
Reef is a modern, statically-typed systems programming language designed for writing safe, concurrent software. It combines:
- Active Objects - Language-level concurrency with automatic synchronization
- Type Safety - Strong static typing with garbage collection
- Modern Features - Generics, exceptions, pattern matching, sum types
- Clean Syntax - Ruby/Crystal inspired readability without whitespace sensitivity
- Systems Capability - Direct hardware access when needed
1.2 Design Goals
- Safe Concurrency - Make concurrent programming as easy as sequential
- Performance - Native code generation with minimal overhead
- Reliability - Catch errors at compile time
- Productivity - Fast compilation, excellent tooling
- Clarity - Code should be obvious and maintainable
1.3 Syntax Philosophy
Reef uses end-based blocks inspired by Ruby and Crystal:
- Blocks end with
endkeyword (not curly braces) - No whitespace sensitivity - indentation is style, not syntax
- Labeled ends for clarity (hybrid approach)
- Named constructs use their name:
end functionName - Control structures use keywords:
end if,end loop
1.4 Target Use Cases
- Backend services and web servers
- Concurrent network applications
- System utilities and tools
- Embedded systems with concurrency needs
- Real-time data processing
- High-performance computing
1.5 Influences
- Active Oberon - Active Objects, type extension
- Modula-3 - Exceptions, generics, interfaces
- Oberon - Simplicity, modules
- Ruby/Crystal - Syntax style, blocks, readability
- Rust - Traits, sum types, pattern matching, explicit unsafe
- Go - Fast compilation, simple concurrency
2. Design Philosophy
2.1 Core Principles
Readability First
- Clear syntax inspired by Ruby/Crystal
- No whitespace sensitivity (indentation is convention)
- Labeled ends prevent confusion
- Self-documenting code
Safety by Default
- Memory safety (GC, bounds checking)
- Type safety (no undefined behavior)
- Concurrency safety (data race freedom)
- Explicit unsafe when needed
Structured Concurrency
- Active Objects as first-class citizens
- Automatic synchronization
- Type-safe message passing
- SMP-aware scheduling
Progressive Disclosure
- Simple programs are simple
- Complex features available when needed
- Learn incrementally
Zero-Cost Abstractions (Where Possible)
- Generics via monomorphization
- Inline procedures
- Compile-time guarantees
2.2 Syntax Decisions
Why END-based blocks?
- Proven by Ruby, Crystal, Lua
- No whitespace sensitivity issues
- Clear block boundaries
- Copy-paste safe
- Refactoring safe
Why Labeled ENDS?
- Eliminates "end waterfall" confusion
- Self-documenting (see what closes what)
- Compiler can verify correctness
- Optional for simple cases
Hybrid Labeling Rules:
- Named things use their name:
end functionName,end TypeName - Control flow uses keywords:
end if,end loop,end match - Simple blocks can omit label if unambiguous
2.3 Non-Goals
- ❌ Replace Rust for bare-metal systems programming
- ❌ Be as simple as Go (we have more features)
- ❌ Support legacy C ABI perfectly
- ❌ Be all things to all people
3. Lexical Structure
3.1 Character Set
Source files: UTF-8 encoding
Identifiers: Unicode letters, digits, underscore
- Must start with letter or underscore
- Case-sensitive
3.2 Keywords
active and as asm await
break case const continue defer
do elif else end enum
exclusive export false finalize fn
for if import in init
interface is let loop match
module mut nil not object
ifarch or package private proc
return run self setof shared
spawn struct switch then trait
true type typecase unless unsafe use
when while with yield
inherited
Notes:
begin,this, andvarare NOT keywords in Reefprocfor procedures (no return value),fnfor functions (returns value)asmfor inline assembly functions and procedures- Exception keywords (
try,catch,raise,raises,finally) are intentionally not implemented - use Result type for recoverable errors,exit(code)for fatal errors deferfor cleanup code at function exit,spawnfor concurrent tasksexit(code)built-in terminates program with specified exit codemutfor mutable variables,letfor immutableselffor object self-referenceunlessfor negative conditionalsrunis a keyword for Active Object bodiestypecaseandinheritedare reserved (0.9)extendsandoverrideare contextual (special only in anobjectdeclaration)- Curly braces
{}are available for special uses - Architecture names (
amd64,arm64,riscv64) are contextual keywords, not reserved words — they're recognized afterforinasm fn ... for <arch>, but are otherwise ordinary identifiers, usable as module path segments, variable names, or parameters.
⚠️ NOT IMPLEMENTED Keywords:
case,switch- Usematchinsteadinterface,trait- Type constraints planned (see ROADMAP.md)with- Context managers not implementedyield- Generators not implementedasyncis NOT a reserved word at all (it's usable as an ordinary identifier) — onlyawaitis implemented, as a monitor-style blocking condition inside Active Object methods (see §8.4), not part of an async/await task-scheduling pattern. ✅ IMPLEMENTED Keywords (v1.6):asm- Inline assembly:asm fn,asm proc(v1.6.0)do- Do-while and do-until loops:do while cond ... end do,do until cond ... end dountil- Do-until loop termination keywordwhen- Match guards:_ when condition =>(v1.4.0)await- Blocks inside anexclusive/sharedActive Object method until a condition becomes true (see §8.4)
3.3 Operators and Delimiters
+ - * / % ** (power)
== != < > <= >=
&& || ! & | ^ ~ (bitwise)
<< >> (shift)
= := += -= *= /= (assignment)
-> => :: ... (misc)
( ) [ ]
. , ; :
Note: Curly braces {} are available for special uses (like inline code, maps)
3.4 Comments
Reef supports single-line and multi-line comments:
// Single-line comment
/*
Multi-line comment
/* Nested comments allowed */
*/
3.4.1 Documentation Comments
Reef supports inline documentation using standard comment syntax with markdown formatting. Documentation comments are extracted by reefc doc to generate API documentation.
Syntax:
- Use regular
//or/* */comments - Place immediately before declarations (no blank lines)
- Support full markdown syntax
Example:
// Calculates the factorial of n.
//
// ## Parameters
// - `n`: A non-negative integer
//
// ## Returns
// The factorial of n (n!)
//
// ## Example
// ```reef
// let result = factorial(5) // Returns 120
// ```
fn factorial(n: int): int
if n <= 1
1
else
n * factorial(n - 1)
end if
end factorial
Special Sections:
Documentation comments can include special markdown sections:
## Example/## Examples- Usage examples## Parameters- Parameter descriptions## Returns- Return value description## Thread Safety- Concurrency notes (for Active Objects)## Panics- Panic conditions## See Also- Cross-references
Header Exclusion:
The first block comment in a file is automatically excluded from documentation (assumed to be copyright/license header). Use // @doc marker to explicitly control where documentation extraction begins.
/*
****************************************************************************
Copyright Header - Automatically excluded
*****************************************************************************/
// Module: mymodule
//
// This documentation will be extracted.
Development Comments:
Comments starting with development markers are excluded from documentation:
TODO:,FIXME:,BUG:,HACK:,XXX:,NOTE:,DEBUG:,OPTIMIZE:,REFACTOR:
// TODO: Add error handling - This line excluded
// Gets the value atomically. - This line included
fn get(): int
self.value
end get
Module-Level Documentation:
Place module documentation at the top of the file (after copyright header):
// Module: io.console
//
// Console input/output operations.
//
// Provides functions for reading from stdin and writing to
// stdout/stderr with proper UTF-8 encoding.
export
proc println
end export
Generating Documentation:
Use reefc doc to generate markdown API documentation:
reefc doc # Generate docs for current project
reefc doc --output api # Specify output directory
See Documentation Generation for complete details.
3.5 Block Terminators
All blocks end with end keyword:
proc example()
// function body
end example
if condition
// body
end if
loop
// body
end loop
3.6 Literals
Integer Literals
42 // decimal
0x2A // hexadecimal
0o52 // octal
0b101010 // binary
1_000_000 // underscores allowed for readability
Floating-Point Literals
3.14
2.5e10
1.5e-8
0.1_234
String Literals
"Hello, world!"
"Line 1\nLine 2" // Escape sequences
r"C:\path\to\file" // Raw string (no escapes, no interpolation)
// String interpolation
let name = "World"
let count = 42
"Hello, ${name}!" // "Hello, World!"
"Count: ${count + 1}" // "Count: 43" — expression interpolation
"Price: \$${price}" // Escape $ with \$
// Multi-line strings ✅ IMPLEMENTED
let text = """
Multi-line string
with automatic dedent
Can include ${variables} too
"""
// Triple quotes, automatic leading whitespace removal, interpolation support
String Escape Sequences:
\n- newline\t- tab\r- carriage return\\- backslash\"- double quote\$- dollar sign (to avoid interpolation)\u{XXXX}- Unicode code point
String Interpolation: ✅ IMPLEMENTED (variables and expressions)
- Use
${varname}to embed variables in strings - Use
${expr}to embed arbitrary expressions (e.g.,${count + 1},${arr.length()}) - Works in both regular strings and multi-line strings
- Does NOT work in raw strings (
r"...") - To include a literal
$, use\$
Multi-Line Strings: ✅ IMPLEMENTED
- Use triple quotes (
""") for multi-line strings - Automatic dedentation based on closing
""" - Supports string interpolation with
${variable} - All escape sequences work (
\n,\t,\\, etc.)
Character Literals
'a'
'\n'
'\u{1F600}' // Unicode
Boolean Literals
true
false
Nil Literal
nil // Null pointer/reference (ONLY in unsafe code)
Important: nil is restricted to unsafe blocks only. In safe code, use Option[T] to represent absence of a value.
Safe code example:
import core.option
fn findUser(id: int, users: [User]): Option[User]
let count = users.length()
for i in 0 to count
if users[i].id == id
return Option_Some(users[i])
end if
end for
return @Option[User].None() // Use Option[User].None(), not nil
end findUser
(There is no if let in Reef — use match, or option.is_some/option.unwrap as shown in 035_ERROR_HANDLING.md, to work with the returned Option[User].)
Unsafe code example:
unsafe
let ptr: *int = nil // nil is allowed here
if ptr != nil
// use *ptr
end if
end unsafe
4. Types and Values
4.1 Type System Overview
Reef has a static, strong, structural type system with:
- Primitive types
- Composite types (arrays, structs, objects)
- Reference types (pointers, object references)
- Generic types
- Sum types (enums with data)
- Trait types (interfaces)
4.2 Primitive Types
Integer Types
int8, int16, int32, int64 // Signed
uint8, uint16, uint32, uint64 // Unsigned
int // C `int` -- always 32-bit, by design
// (not platform-dependent; see 015_BASICS.md)
byte // Alias for uint8 (useful for byte arrays)
Floating-Point Types
float32 // IEEE 754 single precision
float64 // IEEE 754 double precision
Boolean Type
bool // true or false
Character Type
char // Unicode code point (32-bit)
String Type
string // UTF-8 encoded, immutable
Unit Type
unit // Type with single value () - like void but safer
4.3 Composite Types
Arrays
Array types: the array type is written [T] — the element type in square
brackets. Array length is a runtime property, not part of the type (there is no
[N]T fixed-size array type, and no separate []T slice type).
type IntArray = [int] // Array-of-int type alias
mut names: [string] // Array of strings
// Array literals
let numbers = [1, 2, 3, 4, 5] // Type inferred: [int]
let empty = new [int](0) // Empty array (an empty literal [] cannot infer T)
// Runtime allocation with new
let size = 100
let arr = new [int](size) // Array of 100 ints
Nested arrays compose ([[int]] is an array of int-arrays). See
reference/030_TYPES.md for the full array reference.
Array/String Slicing Syntax: ✅ IMPLEMENTED
// Slicing extracts a portion using [start..end] syntax
let s = "Hello World"
let hello = s[0..5] // "Hello" (elements 0-4)
let world = s[6..] // "World" (from index 6 to end)
let hel = s[..3] // "Hel" (first 3 elements)
let nums = [10, 20, 30, 40, 50]
let first_three = nums[0..3] // [10, 20, 30]
let last_two = nums[3..] // [40, 50]
Note: Slicing uses .. syntax (not : like Python). The end index is exclusive.
Array Methods: ✅ IMPLEMENTED
Arrays support the following built-in methods. All methods return a new array (immutable style) -- the original array is not modified. The caller reassigns to update:
mut arr = [1, 2, 3]
// append(item) - returns new array with item added at end
arr = arr.append(4) // arr is now [1, 2, 3, 4]
// pop() - returns new array without last element
arr = arr.pop() // arr is now [1, 2, 3]
// remove(index) - returns new array without element at given index
arr = arr.remove(1) // arr is now [1, 3]
Method signatures:
| Method | Description | Returns |
|---|---|---|
arr.append(item) |
Add item to end | New array with item appended |
arr.pop() |
Remove last element | New array without last element |
arr.remove(index) |
Remove element at index | New array without that element |
Note: These methods follow an immutable-style pattern. The old array becomes eligible for garbage collection after reassignment.
Empty Collection Literals ✅ IMPLEMENTED
Empty array and set literals require type annotation since the compiler cannot infer the element type:
// Error: Cannot infer type of empty array
let arr = [] // Compile error
// Correct: Use typed allocation for empty arrays
let arr: [int] = new [int](0)
// Error: Cannot infer type of empty set
let s = {} // Compile error
// Correct: Provide at least one element
let s = {0} // Type inferred as setof[int]
Structs
// Struct definition
type Point = struct
x: float64
y: float64
end Point
// Struct with methods (not active)
type Rectangle = struct
origin: Point
width: float64
height: float64
fn area(): float64
self.width * self.height
end area
fn contains(p: Point): bool
p.x >= self.origin.x &&
p.x <= self.origin.x + self.width &&
p.y >= self.origin.y &&
p.y <= self.origin.y + self.height
end contains
end Rectangle
// Struct literal
mut rect = Rectangle{
origin: Point{x: 0.0, y: 0.0},
width: 10.0,
height: 5.0
}
Struct Literal Validation ✅ IMPLEMENTED
The compiler validates struct literals at compile time:
- All fields must be provided (no missing fields)
- No duplicate fields allowed
- Field types must match struct definition
// Error: Missing field 'y'
let p = Point { x: 10 } // Compile error
// Error: Duplicate field
let p = Point { x: 10, y: 20, x: 30 } // Compile error
// Correct: All fields provided
let p = Point { x: 10, y: 20 } // OK
Passive objects (0.9)
A passive object is a heap class with single inheritance and virtual
methods. It is not an Active Object: no monitor, no run(), no await.
The full walkthrough is 040_OBJECTS.md.
object Widget
x: int
y: int
init(x: int, y: int)
self.x = x
self.y = y
end init
shared proc paint()
println("Widget(${self.x},${self.y})")
end paint
end Widget
object Button extends Widget
label: string
init(x: int, y: int, label: string)
inherited init(x, y)
self.label = label
end init
override shared proc paint()
inherited paint()
println(" label=${self.label}")
end paint
end Button
proc main()
let b = new Button(1, 2, "ok")
let w: Widget = b
w.paint()
end main
Rules in brief: every method is shared or exclusive; override is
mandatory on a redefinition; inherited m(...) is a static call to the
defining class's base; == is reference identity; arrays of objects are
invariant; impl Trait for Class is legal (subclasses inherit the base
impl; trait methods are not virtual — delegate to a virtual of a
different name). Object-typed spawn arguments are rejected. Opt-in
owner checks: reefc --owner-harness.
Set Types
Sets are unordered collections of unique elements from an ordinal type (integers, characters, booleans, or enums). Sets are implemented as efficient bitsets.
// Set type declarations
type Flags = setof[int] // Set of integers (0..31)
type Permissions = setof[Permission] // Set of enum values
type CharSet = setof[char] // Set of characters
// Set literals
let empty: setof[int] = {}
let digits: setof[int] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
let range: setof[int] = {0..9} // Range notation
let mixed: setof[int] = {0, 2, 5..10, 15}
// Set operations
let s1: setof[int] = {1, 2, 3}
let s2: setof[int] = {2, 3, 4}
let union = s1 + s2 // {1, 2, 3, 4}
let intersection = s1 * s2 // {2, 3}
let difference = s1 - s2 // {1}
// Membership test
if 2 in s1
println("2 is in the set")
end if
// Set examples
type FileMode = enum
Read
Write
Execute
end FileMode
let permissions: setof[FileMode] = {Read, Write}
let can_read = Read in permissions // true
Set Operations:
+- Union (OR)*- Intersection (AND)-- Differencein- Membership test
Implementation Notes:
- Sets of 0..31 use
uint32_t(32-bit bitset) - Sets of 0..63 use
uint64_t(64-bit bitset) - Larger integer sets use arrays of uint64
- Enum sets map enum ordinals to bit positions
- Sets are value types (efficient for small sets)
Subrange Types
Subrange types restrict values to a specific range of an ordinal type. Useful for array indices, protocol values, and domain-specific constraints.
// Subrange type declarations
type Digit = subrange 0 9 // Values from 0 to 9
type Percentage = subrange 0 100 // Values from 0 to 100
type Port = subrange 1 65535 // Port numbers
// Alternative syntax with ..
type DayOfMonth = 1..31 // Values from 1 to 31
// Usage in variable declarations
let age: subrange 0 150 = 25
let score: subrange -100 100 = 42
// Type aliases make them reusable
let digit: Digit = 5
let percent: Percentage = 75
// Array indexing with subranges
type SafeArray = array[Digit] of int // Array with exactly 10 elements
let safe_arr: SafeArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
let value = safe_arr[digit] // Bounds checked at compile time
// Function parameters
fn set_month(m: subrange 1 12)
current_month = m
end set_month
Subrange Characteristics:
- Storage: Stored as smallest sufficient integer type (int8, int16, int32, int64)
- Bounds Checking: Runtime checks on assignment and casting
- Compile-time Optimization: Checks eliminated when provably safe
- Type Compatibility: Subranges are compatible with their base integer type
- Error Handling: Out-of-bounds assignment raises RangeError
Built-in Methods (Planned):
Digit.first() // Returns 0
Digit.last() // Returns 9
Digit.count() // Returns 10 (number of values)
Sum Types (Enums)
// Simple enum
type Color = enum
Red
Green
Blue
end Color
// Enum with associated data
type Option[T] = enum
Some(T)
None
end Option
type Result[T, E] = enum
Ok(T)
Err(E)
end Result
// Complex enum
type Message = enum
Quit
Move(int, int) // variants carry positional data, not named fields
Write(string)
ChangeColor(int, int, int)
end Message
// Using enums
proc main()
mut color = Color_Red() // no-payload variants still need ()
mut maybe = Option_Some(42) // type inferred from the argument
mut outcome = @Result[string, string].Ok("success") // no argument to infer E from, so it's explicit
end main
An enum that carries a GC-managed payload (string, array, struct, active
object, or another payload enum) must be kept in function scope as a
module-level let/mut/const — those globals are a checked typecheck
rejection. Enums with no payload, and enums whose payloads are all
scalars, are allowed as module-level let/mut. Heap-embedded payloads
(struct / Active Object / object fields, array elements, closure
captures) are traced as of 0.9 (descriptor ABI v2).
Every enum variant is a global constructor FUNCTION — no-payload variants are called with empty parens (Color_Red()). Generic variants use Type_Variant(value) when the type argument(s) can be inferred from the value (e.g. Option_Some(42)), or the explicit @Type[Args].Variant(value) form when they can't (e.g. constructing None, or a Result variant that only carries one of its two type parameters). See 035_ERROR_HANDLING.md for the full set of construction idioms.
In practice, don't redeclare Option/Result yourself — core.option and core.result already define the canonical generic Option[T] and Result[T, E] shown above. The standard library pairs Result's E with the structured core.error.Error type wherever a failure needs to carry a reason:
import core.result
import core.error as error
fn parse_port(s: string): Result[int, error.Error]
if s == "8080"
return @Result[int, error.Error].Ok(8080)
end if
return @Result[int, error.Error].Err(error.error(ErrorKind_InvalidInput(), "invalid port number"))
end parse_port
Pointers
type IntPtr = *int // Pointer to int
type MutIntPtr = *mut int // Mutable pointer to int
mut x = 42
mut ptr: *int = &x // Take address
mut value = *ptr // Dereference
4.4 Type Aliases
type UserId = uint64
type Callback = fn(int): bool
type StringMap = Map[string, string]
4.5 Type Inference
mut x = 42 // Inferred: int
mut y = 3.14 // Inferred: float64
mut name = "Alice" // Inferred: string
mut items = [1, 2, 3] // Inferred: [3]int
5. Expressions and Operators
5.1 Operator Precedence (highest to lowest)
1. () [] . -> (call, index, field access)
2. ! ~ - + * & (unary)
3. ** (power)
4. * / % (multiplication)
5. + - (addition)
6. << >> (shift)
7. & (bitwise AND)
8. ^ (bitwise XOR)
9. | (bitwise OR)
10. == != < > <= >= (comparison)
11. && (logical AND)
12. || (logical OR)
13. = := += -= etc. (assignment)
5.2 Arithmetic Operators
x + y // Addition
x - y // Subtraction
x * y // Multiplication
x / y // Division
x % y // Modulo
x ** y // Power
-x // Negation
5.3 Comparison Operators
x == y // Equal
x != y // Not equal
x < y // Less than
x > y // Greater than
x <= y // Less than or equal
x >= y // Greater than or equal
5.4 Logical Operators
!x // Logical NOT
x && y // Logical AND (short-circuit)
x || y // Logical OR (short-circuit)
5.5 Bitwise Operators
~x // Bitwise NOT
x & y // Bitwise AND
x | y // Bitwise OR
x ^ y // Bitwise XOR
x << n // Left shift
x >> n // Right shift
5.6 Type Operations
// Type casting
mut x = 42
mut y = float64(x) // Explicit cast
// Sum type (enum) inspection: use match, not `is`
import core.option
mut value: option.Option[int] = Option_Some(42)
match value
Option_Some(x) =>
println("${x}")
end
Option_None() =>
println("nothing")
end
end match
// or, when you just need a boolean check: option.is_some(value) / option.is_none(value)
// Instance check (for objects/Any values) — `is`/`as` apply here, not to enum variants
if shape is Circle
mut c = shape as Circle // Type assertion
println("${c.radius}")
end if
is/as test and assert an object's concrete type (see 030_TYPES.md and any_type_test.reef/any_box_test.reef under reef-compiler/examples/); they do not test enum variants. To check which variant a sum type value holds, use match (as above) or the option.is_some/option.is_none (result.is_ok/result.is_err) helpers.
5.7 String Operations
mut s1 = "Hello"
mut s2 = "World"
mut s3 = s1 + " " + s2 // Concatenation
mut len = str.length(s1) // Length
mut char = s1[0] // Indexing
mut sub = s1[1..4] // Slicing (`..`, end-exclusive — see §4.3)
6. Statements and Control Flow
6.1 Variable Declarations
// Immutable (default)
let x = 42
let name = "Alice"
// Mutable
mut count = 0
count = count + 1
// Explicit type
let ratio: float64 = 0.5
mut items: [string] = []
Not implemented: comma-separated multiple declaration (let x, y = 10, 20), tuple/struct destructuring (mut (a, b) = (1, 2), let Point{x, y} = point), and rest-patterns (let (first, rest...) = list) do not exist — there is no destructuring let. Declare each binding on its own line instead:
let x = 10
let y = 20
let point_x = point.x
let point_y = point.y
6.2 Assignment
x = 42 // Simple assignment
x += 10 // Compound assignment
x -= 5
x *= 2
x /= 2
No multi-target assignment — x, y = y, x is not implemented. Swap through a temporary instead:
let tmp = x
x = y
y = tmp
6.3 If and Unless Statements
If Statement
if condition
// then branch
end if
if condition
// then
else
// else
end if
if condition1
// branch 1
elif condition2
// branch 2
else
// else branch
end if
// Reef has no `if let`. Check an Option/Result with `option.is_some`/
// `option.unwrap` (see 5.6), or handle it with a full `match`:
if option.is_some(optional)
let value = option.unwrap(optional)
print(value)
end if
If Expression (with then keyword)
If expressions return a value and use the then keyword to distinguish them from if statements:
// Basic if expression - returns a value
let max = if a > b then a else b end if
// Use inline in expressions
println(if ready then "Yes" else "No" end if)
// Assign based on condition
let sign = if x < 0 then -1 else 1 end if
// With complex expressions
let result = if str.is_empty(name) then default_name else name end if
Key differences from if statement:
- Uses
thenkeyword after condition - Returns a value (can be assigned or used in expressions)
- Must have
elsebranch (all paths must return a value) - Cannot have
elif(use nested if expressions instead)
Comparison:
| Form | Syntax | Returns Value | Use Case |
|---|---|---|---|
| If statement | if cond ... end if |
No | Control flow |
| If expression | if cond then expr1 else expr2 end if |
Yes | Value selection |
Unless Statement (Negative Conditional)
// unless is the opposite of if
unless condition
// executes when condition is FALSE
end unless
// Equivalent to: if !condition
unless x > 10
print("x is not greater than 10")
end unless
// unless with else
unless userLoggedIn
showLoginScreen()
else
showDashboard()
end unless
// unless is particularly readable for guard clauses
import core.result
import core.error as error
fn read_config(path: string): result.Result[string, error.Error]
unless fileExists(path)
return @Result[string, error.Error].Err(error.error(ErrorKind_NotFound(), "File not found"))
end unless
return @Result[string, error.Error].Ok("...")
end read_config
Note: unless cannot have elif clauses. For multiple conditions, use if instead.
6.4 Match Statement (Pattern Matching)
Enum variant patterns use the unqualified Type_Variant(...) constructor form — including for the generic Result/Option from core.result/core.option (Result_Ok/Result_Err, Option_Some/Option_None), not bare Ok/Err/Some/None. See 035_ERROR_HANDLING.md and 030_TYPES.md.
import core.result
import core.error as error
// Match on a Result
fn divide(a: int, b: int): result.Result[int, error.Error]
if b == 0
return @Result[int, error.Error].Err(error.error(ErrorKind_InvalidInput(), "division by zero"))
end if
return @Result[int, error.Error].Ok(a / b)
end divide
proc main()
let outcome = divide(10, 2)
match outcome
Result_Ok(value) =>
println("Success: ${value}")
end
Result_Err(e) =>
println("Error: ${error.error_message(e)}")
end
end match
end main
// Match with complex patterns — a custom enum with tuple-style variants
// (Reef enum variants carry positional data, not named/struct fields)
type Message = enum
Quit
Move(int, int)
Write(string)
ChangeColor(int, int, int)
end Message
proc handle(message: Message)
match message
Message_Quit() =>
println("Quitting")
end
Message_Move(x, y) =>
println("Move to (${x}, ${y})")
end
Message_Write(text) =>
println("Text: ${text}")
end
Message_ChangeColor(r, g, b) =>
println("Color: RGB(${r}, ${g}, ${b})")
end
end match
end handle
// Match with guards ✅ IMPLEMENTED
// Guards use the `when` keyword after the pattern
// NOTE: Guards work with external variables, not pattern-bound variables
fn categorize(n: int): string
match n
_ when n < 0 =>
return "negative"
end
_ when n == 0 =>
return "zero"
end
_ when n > 100 =>
return "large"
end
_ =>
return "small positive"
end
end match
end categorize
// Multiple conditions in guards
fn in_range(n: int, min: int, max: int): bool
match n
_ when n >= min and n <= max =>
return true
end
_ =>
return false
end
end match
end in_range
Match Expressions (as Values) ✅ IMPLEMENTED
Match can also be used as an expression that returns a value. All arms must return the same type:
// Match expression assigned to variable
let result = match x
1 => 100
2 => 200
_ => 0
end match
// Match expression in function return
fn categorize(n: int): string
match n
0 => "zero"
1 => "one"
_ => "many"
end match
end categorize
// Match expression must have all arms return same type
let doubled = match value
1 => 2
2 => 4
3 => 6
_ => 0
end match
Implementation Notes:
- All match arms must return the same type
- Match expressions use exhaustiveness checking
- Generated as GCC statement-expressions in C output
6.4.1 typecase (passive objects)
Runtime type dispatch over a hierarchy. First matching arm wins. The binder is the narrowed view. Full rules: 040_OBJECTS.md.
object Widget
n: int
init(n: int)
self.n = n
end init
end Widget
object Button extends Widget
init(n: int)
inherited init(n)
end init
end Button
proc tag(w: Widget)
typecase w
Button b =>
println("button ${b.n}")
end
else =>
println("other ${w.n}")
end
end typecase
end tag
proc main()
tag(new Button(1))
end main
6.5 Loops
While Loop
while condition
// body
end while
// While with break
while true
if shouldStop
break
end if
end while
// While with continue
while condition
if shouldSkip
continue
end if
// process
end while
For Loop
// For range with .. syntax
for i in 0..10
print(i) // 0 to 9 (stop is exclusive)
end for
// For range with TO keyword (alternative)
for i in 0 to 10
print(i) // 0 to 9 (stop is exclusive)
end for
// Both .. and to work with literals or variables
let start = 0
let stop = 10
for i in start..stop
print(i) // 0 to 9
end for
// For each - iterates over arrays and strings
for item in collection
print(item)
end for
// For each with index
for i, item in collection
print_int(i) // Index (0, 1, 2, ...)
print(item) // Element value
end for
// For with step
for i in 0 to 100 step 10
print(i) // 0, 10, 20, ..., 90
end for
Loop (Infinite)
loop
// infinite loop
if condition
break
end if
end loop
Do-While and Do-Until ✅ IMPLEMENTED
// Do-while: body executes at least once, repeats while condition is true
do while condition
// body
end do
// Do-until: body executes at least once, repeats until condition becomes true
do until condition
// body
end do
Both do while and do until guarantee the body executes at least once before the condition is checked. break and continue work inside do-loops.
6.6 Control Transfer
break // Exit loop
continue // Next iteration
return // Return from function
return value // Return value
7. Procedures and Functions
7.1 Procedures vs Functions
Reef distinguishes between procedures (no return value) and functions (returns a value):
proc: A procedure that performs actions but returns nothingfn: A function that computes and returns a value
Procedure Definition
// Procedure - no return value
proc printMessage(msg: string)
println(msg)
end printMessage
proc greet(name: string)
println("Hello, ${name}!")
end greet
// Using return in proc (no value)
proc validate(x: int)
unless x >= 0
println("Error: negative value")
return // Early return (no value)
end unless
println("Valid: ${x}")
end validate
// ERROR: Cannot return value from proc
proc broken()
return 42 // COMPILE ERROR!
end broken
Function Definition
import core.option
// Simple function - returns value
fn add(a: int, b: int): int
a + b // Last expression is returned
end add
// Explicit return
fn square(x: int): int
return x * x
end square
// Multiple return values ⚠️ NOT IMPLEMENTED
// > Status: No tuple types, so a function cannot return `(int, int)`.
// > Return a struct instead:
type DivMod = struct
quotient: int
remainder: int
end DivMod
fn divmod(a: int, b: int): DivMod
DivMod{quotient: a / b, remainder: a % b}
end divmod
// Absence is Option[T], never a nil-returning pointer (see 5.6 / 035_ERROR_HANDLING.md)
fn findFirst(list: [int], target: int): option.Option[int]
let len = list.length()
for i in 0 to len
if list[i] == target
return Option_Some(i)
end if
end for
return @Option[int].None()
end findFirst
// Default parameters ✅ IMPLEMENTED
fn greet(name: string, greeting: string = "Hello"): string
return "${greeting}, ${name}!"
end greet
// Multiple defaults - must come after required parameters
fn format_number(n: int, prefix: string = "", suffix: string = ""): string
return "${prefix}${n}${suffix}"
end format_number
// Works with generic functions too (type inference and [:Type] explicit
// arguments both work with default parameters)
fn wrap_with_label[T](value: T, label: string = "value"): string
return label
end wrap_with_label
proc main()
println(greet("World")) // Uses default: "Hello, World!"
println(greet("World", "Hi")) // Overrides: "Hi, World!"
println(wrap_with_label[:int](42)) // "value" (uses default)
println(wrap_with_label[:int](42, "number")) // "number"
let x: int = 42
println(wrap_with_label(x)) // "value" (infers T=int)
println(wrap_with_label(x, "count")) // "count"
end main
// Variadic parameters ⚠️ NOT IMPLEMENTED
// > Status: Not Implemented. Use arrays to pass multiple values.
fn sum(numbers: ...int): int
mut total = 0
for n in numbers
total += n
end for
total
end sum
// Note: Variadic parameters planned - see ROADMAP.md
// Generic function with type constraints ⚠️ NOT IMPLEMENTED
// > Status: `T: Comparable` bound syntax does not parse yet — planned,
// > see reference/055_GENERICS.md. Today, write a concrete-type function
// > (or an unconstrained fn[T] for operations that don't need comparison).
fn max(a: int, b: int): int
if a > b
return a
end if
return b
end max
7.2 Expression-Oriented
A single trailing expression is returned implicitly — no return needed:
fn square(x: int): int
x * x // Returns x * x
end square
// Explicit return still available, and required once the function body
// branches (an `if`/`else` block as the final statement does NOT act as
// an implicit return — use explicit `return` in each branch, or use the
// `if ... then ... else ... end if` expression form from 6.3):
fn max(a: int, b: int): int
if a > b
return a
end if
return b
end max
fn earlyReturn(x: int): int
if x < 0
return 0
end if
x * 2 // trailing expression, implicit return
end earlyReturn
7.3 Function Types
Function types use Fn[params..., return] — the last type argument is the return type (square brackets avoid ambiguity with a parameter list). See 060_CLOSURES.md.
type BinaryOp = Fn[int, int, int] // (int, int) -> int
type Predicate = Fn[int, bool] // int -> bool
A variable or parameter typed directly as Fn[...] can be called; assign it a lambda (a bare top-level function name is not itself an Fn-typed value):
fn add(a: int, b: int): int
a + b
end add
proc main()
let operation: Fn[int, int, int] = fn(a: int, b: int): int => add(a, b)
let result = operation(5, 3)
println("${result}")
end main
7.4 Closures and Lambda Expressions ✅ IMPLEMENTED
Reef supports full closures with variable capture, mutable captures, and escape analysis optimization.
Lambda Expressions
Expression-body lambdas (single expression):
// Function lambda with explicit return type
let double = fn(x: int): int => x * 2
// Function lambda with inferred return type
let triple = fn(x: int) => x * 3
// Multi-parameter lambda
let add = fn(a: int, b: int): int => a + b
// Procedure lambda (no return value)
let printer = proc(x: int) => println("${x}")
Block-body lambdas (multi-statement):
// Function with block body
let complex = fn(x: int): int
let y = x * 2
let z = y + 1
return z
end fn
// Implicit return (last expression)
let square_plus_one = fn(n: int): int
let sq = n * n
sq + 1 // returned
end fn
// Procedure with block body
let logger = proc(msg: string)
print("LOG: ")
println(msg)
end proc
Variable Capture
Immutable capture (copied by value):
let factor = 10
let scale = fn(x: int): int => x * factor
println("${scale(5)}") // 50
Mutable capture (boxed by reference):
mut counter = 0
let inc = fn(): int
counter = counter + 1
counter
end fn
println("${inc()}") // 1
println("${inc()}") // 2
println("${counter}") // 2 (mutated by closure)
Function Types
Function types use Fn[params..., return] syntax:
// Fn[int, int] = function taking int, returning int
type Mapper = Fn[int, int]
// Fn[int, int, int] = function (int, int) -> int
type Reducer = Fn[int, int, int]
// Fn[int] = procedure taking int (returns unit)
type Consumer = Fn[int]
Higher-Order Functions
// Map function
fn list_map(items: [int], f: Fn[int, int]): [int]
let len = items.length()
mut result = new [int](len)
for i in 0 to len
result[i] = f(items[i])
end for
result
end list_map
// Foreach procedure
proc list_foreach(items: [int], f: Fn[int])
let len = items.length()
for i in 0 to len
f(items[i])
end for
end list_foreach
// Usage
let doubled = list_map([1, 2, 3], fn(x: int): int => x * 2)
list_foreach([1, 2, 3], proc(n: int) => println("${n}"))
Escape Analysis Optimization
The compiler performs escape analysis to optimize closure allocation:
- Non-escaping closures (passed directly to functions): stack-allocated
- Escaping closures (stored in variables or returned): heap-allocated
// Stack-allocated (immediate use)
list_foreach(items, proc(n: int) => println("${n}"))
// Heap-allocated (stored in variable)
let printer = proc(n: int) => println("${n}")
list_foreach(items, printer)
7.5 Methods
There is no static keyword — a "static"/associated constructor is just a regular module-level function that returns the struct:
type Counter = struct
value: int
// Method
proc increment()
self.value += 1
end increment
// Method with parameters
proc add(amount: int)
self.value += amount
end add
end Counter
// Associated constructor: an ordinary function, not a method on Counter
fn new_counter(): Counter
Counter{value: 0}
end new_counter
proc main()
mut counter = new_counter()
counter.increment()
counter.add(5)
println("${counter.value}")
end main
8. Active Objects (Concurrency Model)
8.1 Overview
Active Objects are the core concurrency primitive in Reef. Each active object:
- Runs in its own thread
- Has its own state
- Automatically synchronizes method calls (
exclusive/shared) - Provides data-race freedom guarantees
See reference/045_ACTIVE_OBJECTS.md for the full walkthrough (fields, init/finalize, exclusive/shared, run(), generic AOs, best practices); this section summarizes the model and its idioms, and covers coordination patterns (await, hand-off objects) in more depth.
8.2 Active Object Definition
active object Counter
value: int
// Constructor
init()
self.value = 0
end init
// Exclusive method (automatic mutual exclusion)
exclusive proc increment()
self.value += 1
end increment
exclusive fn get(): int
return self.value
end get
// Active body -- runs once in its own thread when the object is created
run()
println("Counter thread started")
end run
end Counter
proc main()
mut counter = new Counter() // Automatically starts the run() thread
counter.increment() // Thread-safe call
println("Value: ${counter.get()}")
end main
8.3 Exclusive and Shared Methods
exclusive methods provide automatic mutual exclusion — only one thread may execute an exclusive method on a given object at a time. shared methods allow concurrent readers as long as no exclusive method is running:
active object Database
connection_count: int
// Only one thread can execute exclusive methods at a time
exclusive proc add_connection()
self.connection_count += 1
end add_connection
exclusive proc remove_connection()
self.connection_count -= 1
end remove_connection
// Multiple readers allowed (no modification)
shared fn get_connection_count(): int
return self.connection_count
end get_connection_count
end Database
proc main()
let db = new Database()
db.add_connection()
db.add_connection()
db.remove_connection()
println("Connections: ${db.get_connection_count()}")
end main
Both rules above are compiler-enforced, not just conventions. A shared
method that assigns to self state is a compile-time Type Error:
Type Error: Cannot assign to Active Object state in a 'shared' method.
Shared methods take a reader lock and may run concurrently, so mutating
'self' would be a data race. Use an 'exclusive' method to modify fields.
And a shared method calling an exclusive method on self is also a
compile-time Type Error — it deadlocked silently at runtime before this was
caught statically:
Type Error: Cannot call exclusive method 'bump' on 'self' from a shared
method. The shared reader lock blocks the exclusive acquisition, so the
Active Object would deadlock. Make the calling method 'exclusive', or move
the shared portion into a separate shared method.
8.4 Active Body and await
The active body (run()) is the object's main execution loop, started automatically when the object is created (after init(), before the constructor returns to the caller). Inside exclusive methods, await condition blocks the calling thread until condition becomes true, releasing the object's lock while it waits — this is Reef's primitive for passive coordination between threads, in place of manual polling. await is only valid inside exclusive methods. There is no if let in Reef — test an Option/Result value with match, or with option.is_some/result.is_ok (see reference/035_ERROR_HANDLING.md).
await blocks whenever its condition is not yet true, including when called
directly from main(). Await conditions may reference the enclosing method's
parameters and locals as well as self.*. One restriction applies: await
written lexically inside run() is rejected at typecheck, because run()
executes outside the object's monitor — put it in an exclusive fn/proc
that run() calls instead. See
reference/045_ACTIVE_OBJECTS.md
for the full workaround idiom and a shipped example.
active object Worker
running: bool
init()
self.running = true
end init
exclusive proc stop()
self.running = false
end stop
// Active body -- background loop; exits once stop() flips the flag
run()
loop
if !self.running
break
end if
end loop
println("Worker thread exiting")
end run
end Worker
proc main()
let worker = new Worker()
worker.stop()
println("Requested stop")
end main
8.5 Coordinating Active Objects
Reef has no built-in Channel[T] type. Active Objects communicate by holding references to each other as fields and calling each other's methods (self.field.method() chains through nested Active Object fields), and by using await inside exclusive methods as the hand-off point. A single-slot hand-off — the building block for a producer/consumer pipeline — looks like this:
active object IntChannel
value: int
has_data: bool
init()
self.value = 0
self.has_data = false
end init
exclusive proc send(v: int)
await self.has_data == false
self.value = v
self.has_data = true
end send
exclusive fn receive(): int
await self.has_data == true
let v = self.value
self.has_data = false
return v
end receive
end IntChannel
proc main()
let ch = new IntChannel()
ch.send(42)
println("Received: ${ch.receive()}")
ch.send(99)
println("Received: ${ch.receive()}")
end main
A producer and a consumer can each hold a reference to a hand-off object like IntChannel and drive it from their own run() bodies:
import time.clock as clock
active object Producer
channel: IntChannel
init(ch: IntChannel)
self.channel = ch
end init
run()
for i in 0 to 5
self.channel.send(i)
end for
end run
end Producer
active object Consumer
channel: IntChannel
total: int
init(ch: IntChannel)
self.channel = ch
self.total = 0
end init
exclusive fn get_total(): int
return self.total
end get_total
run()
for i in 0 to 5
let v = self.channel.receive()
self.total = self.total + v
end for
end run
end Consumer
proc main()
let ch = new IntChannel()
let producer = new Producer(ch)
let consumer = new Consumer(ch)
clock.sleep_millis(200) // let the background threads finish
println("Consumer total: ${consumer.get_total()}") // 0+1+2+3+4 = 10
end main
8.6 Networking Sketch: TCP Server (Idiom Reference, Not Compiled)
net.tcp is function-based, not object-oriented: sockets are plain int file descriptors, and its ten fallible operations (tcp_listen, tcp_accept, tcp_send*, tcp_recv*) return result.Result[T, core.error.Error] rather than a Connection object. The sketch below wraps that API in an Active Object to show the shape of a server built on Active Objects. A real accept loop blocks waiting for connections, so — unlike the examples above — this one is not run as part of this document's verification; its call shapes were checked line-by-line against reef-compiler/examples/test_tcp.reef and the exported signatures in reef-stdlib/net/tcp.reef.
module server
import net.tcp
import core.result as result
import core.error as error
active object WebServer
port: int
server_fd: int
running: bool
init(port: int)
self.port = port
self.server_fd = -1
self.running = true
end init
exclusive proc stop()
self.running = false
end stop
run()
let listen_r = tcp.tcp_listen(self.port, 16)
if result.is_err(listen_r)
println("Failed to listen: ${error.error_message(result.unwrap_err(listen_r))}")
return
end if
self.server_fd = result.unwrap_ok(listen_r)
println("Server listening on port ${self.port}")
while self.running
match tcp.tcp_accept(self.server_fd)
Result_Ok(client_fd) =>
self.handle_client(client_fd)
end
Result_Err(e) =>
println("Accept error: ${error.error_message(e)}")
end
end match
end while
tcp.tcp_close(self.server_fd)
println("Server stopped")
end run
exclusive proc handle_client(client_fd: int)
let data_r = tcp.tcp_recv(client_fd, 4096)
if result.is_ok(data_r)
tcp.tcp_send(client_fd, "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")
end if
tcp.tcp_close(client_fd)
end handle_client
end WebServer
proc main()
let server = new WebServer(8080)
// ... server runs in the background; call server.stop() to shut it down
end main
end module
9. Modules and Packages
9.1 Module System
Module Definition
Modules use export sections (Modula-2 style) to define public interface:
// File: math/geometry.reef
module math.geometry
import math.basic as math
// Export section - defines public interface
export
type Point
fn distance(p1: Point, p2: Point): float
end export
// Public type implementation
type Point = struct
x: float
y: float
end Point
// Public function implementation
fn distance(p1: Point, p2: Point): float
let dx = p2.x - p1.x
let dy = p2.y - p1.y
return math.sqrt_f(dx*dx + dy*dy)
end distance
// PRIVATE - not in export section (module-local only)
proc helper()
// Internal helper function - not accessible from outside
end helper
end module
Key points:
- Export section lists public interface (signatures only)
- Functions not in export section are PRIVATE (module-local)
- Implementations come after export section
- Private functions can call each other within module
- Clear separation of interface and implementation
Importing
Reef only imports whole modules — there is no selective/braced import (import module.{A, B} does not exist). By default, the module's last dotted path segment becomes the qualifier used to access its exports; as overrides it:
// Import entire module -- default qualifier is the last path segment ("geometry")
import math.geometry
mut p = geometry.Point{x: 0.0, y: 0.0}
// Import with alias -- qualifier becomes "geom"
import math.geometry as geom
mut p = geom.Point{x: 0.0, y: 0.0}
// Conditional imports by architecture
ifarch amd64
import drivers.lapic as lapic
import drivers.hpet as timer
end ifarch
ifarch riscv64
import drivers.plic as plic
import drivers.timer as timer
end ifarch
9.2 Project Structure and reef.toml
Overview
Reef projects use a standardized structure with a reef.toml configuration file. This ensures compatibility across different Reef compiler implementations and tooling.
reef.toml Format
File format: TOML (Tom's Obvious, Minimal Language) Location: Project root directory Encoding: UTF-8
Required Fields
Every reef.toml MUST contain these fields:
[package]
name = "myproject" # Project/package name (lowercase, alphanumeric, -, _)
version = "1.0.0" # Semantic versioning (major.minor.patch)
author = "Name <email@example.com>" # Primary author with email
description = "Brief description" # One-line project summary
Recommended Fields
license = "MIT" # SPDX license identifier
url = "https://github.com/user/myproject" # Project homepage or repository
Optional Build Configuration
[build]
entry = "src/main.reef" # Entry point file (default: main.reef)
output = "myapp" # Binary name (default: package.name)
output_dir = "build" # Build artifacts directory (default: build/)
source_dirs = ["src", "lib"] # Source directories (default: ["src"])
optimize = 2 # Optimization level 0-3 (default: 2)
Future: Package Dependencies
[dependencies]
stdlib = "1.0" # Reef standard library version
network = "2.1.0" # Third-party package
json = { version = "1.5", features = ["fast"] }
Note: Package manager not yet implemented in Reef 1.0
Standard Project Structure
myproject/
├── reef.toml # Project definition (REQUIRED)
├── src/ # Source code (convention)
│ ├── main.reef # Entry point (proc main() or fn main(): int)
│ └── *.reef # Additional source files
├── build/ # Build artifacts (created by compiler)
│ ├── myproject # Compiled binary
│ └── *.c # Generated C code
├── tests/ # Test files (convention)
│ └── test_*.reef
├── docs/ # Generated API documentation (reefc doc)
│ ├── index.md # Documentation landing page
│ ├── modules/ # Module documentation
│ ├── types/ # Type documentation
│ └── functions/ # Function documentation
├── .gitignore # Should include: build/
└── README.md # Project documentation
Build Directory Convention
The compiler MUST output all build artifacts to the build/ directory:
- Compiled binaries:
build/<package.name> - Generated C code:
build/<package.name>.c - Object files:
build/*.o
The build/ directory:
- Is created automatically by the compiler
- MUST NOT be checked into version control
- Can be cleaned with
reefc clean(future)
Compiler Commands
Project-based compilation:
reefc build # Build project using reef.toml
reefc run # Build and run project
Single-file compilation:
reefc program.reef # Compile single file (no reef.toml needed)
reefc run program.reef # Compile and run single file
Project scaffolding:
reefc new <name> # Create new project with reef.toml
reefc init # Initialize reef.toml in existing directory
Example reef.toml Files
Minimal executable:
[package]
name = "hello"
version = "0.1.0"
author = "Chris Tusa <chris@example.com>"
description = "Hello world program"
license = "MIT"
url = "https://github.com/user/hello"
Application with build options:
[package]
name = "webserver"
version = "2.0.0"
author = "Web Team <team@example.com>"
description = "High-performance web server"
license = "Apache-2.0"
url = "https://github.com/company/webserver"
[build]
entry = "src/main.reef"
output = "webserver"
optimize = 3
Library package (future):
[package]
name = "reef-network"
version = "2.0.0"
author = "Network Team <team@example.com>"
description = "Networking library for Reef"
license = "MIT"
url = "https://github.com/reef-lang/network"
[lib]
path = "src/lib.reef"
[dependencies]
stdlib = "1.0"
Compatibility Requirements
All Reef compiler implementations MUST:
- Support reef.toml with required fields
- Use
build/directory for output by default - Support
reefc buildcommand - Support
reefc --versionandreefc --help
All Reef compiler implementations SHOULD:
- Support
reefc runcommand - Support
reefc newandreefc initcommands - Support recommended fields (license, url)
9.5 Module-Level Scope ✅ IMPLEMENTED
Module-level declarations allow defining constants, variables, and mutable state at the module scope.
9.5.1 Constants (const)
Constants are compile-time evaluated immutable values:
module mymodule
const PI: float = 3.14159265358979
const MAX_SIZE: int = 1024
const APP_NAME: string = "MyApp"
const DEBUG: bool = true
fn circumference(radius: float): float
return 2.0 * PI * radius
end circumference
end module
Properties:
- ✅ Must be initialized with compile-time evaluable expression
- ✅ Type can be inferred or explicitly annotated
- ✅ Can be exported:
const PI: floatin export section - ✅ Generated as
static constin C output
9.5.2 Module Variables (let)
Module-level immutable variables, initialized at runtime:
module config
let default_timeout: int = 30
let version: string = "1.0.0"
fn get_timeout(): int
return default_timeout
end get_timeout
end module
Properties:
- ✅ Initialized once at module load time
- ✅ Immutable after initialization (cannot be reassigned)
- ✅ Can be exported:
let timeout: intin export section - ✅ Generated as
staticC variable
9.5.3 Module Mutable State (mut)
Module-level mutable state persists across function calls:
module math.random
// Module-level mutable state for PRNG
mut rng_state: int = 2463534242
fn xorshift32(): int
rng_state = rng_state ^ (rng_state << 13)
rng_state = rng_state ^ (rng_state >> 17)
rng_state = rng_state ^ (rng_state << 5)
return rng_state
end xorshift32
proc seed(s: int)
rng_state = s
end seed
end module
Properties:
- ✅ Mutable - can be assigned from any function in the module
- ❌ NOT exportable - by design, prevents external modification
- ✅ Single-threaded semantics (no thread safety guarantees)
- ✅ Generated as
staticC variable
Warning — Thread Safety: Module-level
mutvariables have no thread-safety guarantees. They must not be accessed from multiple Active Objects without external synchronization. Concurrent reads and writes to module-levelmutstate from different Active Objects will cause data races. Use Active Object fields with exclusive/shared methods for thread-safe mutable state.
9.5.4 Design Rationale
| Declaration | Mutability | Exportable | Use Case |
|---|---|---|---|
const |
Immutable (compile-time) | Yes | True constants like PI, MAX_SIZE |
let |
Immutable (runtime) | Yes | Configuration values, computed once |
mut |
Mutable | No | Internal state like PRNG seed, counters |
Thread Safety: Module-level mut variables provide NO thread safety guarantees.
Use Active Objects for concurrent access to mutable state.
10. Generics
10.1 Generic Functions ✅ IMPLEMENTED
Type parameters in square brackets:
// Generic function with single type parameter
fn identity[T](value: T): T
return value
end identity
// Call with explicit type arguments
// Note: Use [:Type] syntax to disambiguate from array access
let x = identity[:int](42)
let s = identity[:string]("hello")
// Multiple type parameters
fn first[T, U](a: T, b: U): T
return a
end first
let f = first[:int, string](100, "ignored") // Returns 100
Implementation Status (Reef 1.7.0):
- ✅ Generic function definitions with type parameters
- ✅ Explicit type arguments at call site with
[:Type]syntax - ✅ Type inference from arguments (when unambiguous)
- ✅ Multiple type parameters supported
- ✅ Compile-time monomorphization (zero runtime overhead)
- ✅ Full type safety with parameter substitution
- ✅ Type constraints with
whereclause
10.1.1 Type Constraints ✅ IMPLEMENTED
Type constraints restrict generic type parameters to types that implement specific traits:
// Generic function with trait constraint
fn describe[T](item: T): int where T: Printable
42
end describe
// Multiple traits with + separator
fn process[T](item: T): T where T: Printable + Comparable
item
end process
// Multiple type parameter constraints
fn combine[T, U](a: T, b: U): T where T: Printable, U: Comparable
a
end combine
When a constrained generic function is called, the type checker validates that the provided type implements all required traits. If not, a compile-time error is raised.
10.2 Generic Types
Generic structs are instantiated with new Type[Args](), not a struct literal — the literal form (Type{field: value}) does not take type arguments:
// Generic struct
type Box[T] = struct
value: T
fn get(): T
return self.value
end get
proc set(value: T)
self.value = value
end set
end Box
proc main()
mut int_box: Box[int] = new Box[int]()
int_box.set(42)
mut str_box: Box[string] = new Box[string]()
str_box.set("hello")
println("int_box: ${int_box.get()}")
println("str_box: ${str_box.get()}")
end main
10.3 Generic Active Objects
Active Objects can also take type parameters. Array-of-T fields use [T], not []T, and constructing a generic Option[T] without an existing value to infer from needs the explicit form @Option[T].None():
active object Queue[T]
items: [T]
init()
self.items = new [T](0)
end init
exclusive proc enqueue(item: T)
self.items.append(item)
end enqueue
exclusive fn dequeue(): Option[T]
if self.items.length == 0
return @Option[T].None()
else
let item = self.items[0]
self.items = self.items[1..]
return @Option[T].Some(item)
end if
end dequeue
end Queue
mut queue = new Queue[string]()
queue.enqueue("first")
queue.enqueue("second")
10.4 Traits ✅ IMPLEMENTED
Traits define shared behavior (interfaces) that types can implement. They are used for type constraints on generic functions.
10.4.1 Trait Definition
// Simple trait with abstract method
trait Printable
proc print(); // Semicolon marks abstract method (no body)
end Printable
// Trait with multiple methods
trait Comparable
fn compare(other: int): int;
fn equals(other: int): bool;
end Comparable
// Trait with return type
trait Hashable
fn hash(): int;
end Hashable
Syntax Notes:
- Abstract methods end with a semicolon (
;) instead of a body - Methods can be either
fn(with return) orproc(no return) - The
selfparameter is implicit - method receives the implementing type's instance
10.4.2 Trait Implementation
// Struct that will implement traits
type Point = struct
x: int
y: int
end Point
// Implement Printable for Point
impl Printable for Point
proc print()
println("Point(${self.x}, ${self.y})")
end print
end impl
// Implement Comparable for Point
impl Comparable for Point
fn compare(other: int): int
let sum = self.x + self.y
if sum < other
return -1
elif sum > other
return 1
else
return 0
end if
end compare
fn equals(other: int): bool
(self.x + self.y) == other
end equals
end impl
Implementation Requirements:
- All abstract trait methods must be implemented
- Method signatures must match exactly (parameter count, types, return type)
- The compiler validates signature matches at compile time
10.4.3 Using Traits with Generics
// Generic function constrained to Printable types
proc print_item[T](item: T) where T: Printable
item.print() // Calls trait method — resolved at compile time via monomorphization
end print_item
// Usage — dot notation dispatches to correct trait impl
let p = Point { x: 10, y: 20 }
p.print() // Direct: calls Point's Printable.print()
print_item(p) // Generic: monomorphizes to print_item_Point, calls reef_Point_print()
// Multiple trait constraints
proc show_info[T](item: T) where T: Printable + Describable
item.print()
println(item.describe())
end show_info
Implementation: Trait method dispatch uses static dispatch via monomorphization. Generic functions are specialized per concrete type at compile time. No vtables or dynamic dispatch —
print_item[Point]compiles to a direct call toreef_Point_print().
11. Error Handling and Concurrency Primitives
11.1 Error Handling with Result Type
Reef uses the Result[T, E] pattern for explicit error handling instead of exceptions.
This makes error paths visible in type signatures and requires explicit handling.
Result[T, E] (from core.result) and Option[T] (from core.option) are both
generic — a single type definition each, not a family of hand-rolled per-domain
enums. The standard error payload is core.error.Error{kind, message, code}, paired
with the ErrorKind enum; see reference/035_ERROR_HANDLING.md
for the full construction/accessor idioms.
As a convention: reach for Option[T] when a value may simply be absent
(no reason needed), and Result[T, E] when an operation can fail and the
caller needs to know why. Extracting the wrong variant — option.unwrap on
a None, result.unwrap_ok on an Err, etc. — panics (aborts the process)
rather than returning a fallback; see §11.4 below
and reference/028's "Design Guidelines" for the full decision criteria.
import io.file
import core.result as result
import core.error as error
// Function that can fail returns Result[T, error.Error] -- not a hand-rolled
// per-domain error enum
fn open_file(path: string): result.Result[string, error.Error]
return file.readFile(path)
end open_file
// Handling errors with match -- the canonical patterns are Result_Ok/Result_Err
proc process()
match open_file("data.txt")
Result_Ok(contents) =>
println("File opened successfully")
end
Result_Err(e) =>
match error.error_kind(e)
ErrorKind_NotFound() =>
println("File not found")
end
ErrorKind_PermissionDenied() =>
println("Access denied")
end
_ =>
println("Error: ${error.error_message(e)}")
end
end match
end
end match
end process
proc main()
process()
end main
11.2 Defer Statement
The defer statement schedules cleanup code to execute when the function returns.
Multiple defers execute in LIFO (last-in, first-out) order.
proc with_resource()
let resource = acquire_resource()
defer
release_resource(resource)
end defer
// Use resource...
// Cleanup runs automatically at function exit
end with_resource
// Multiple defers - LIFO order
proc multiple_defers()
defer
println("First registered, last to run")
end defer
defer
println("Second registered, runs first")
end defer
println("Function body")
end multiple_defers
// Output: Function body, Second..., First...
Defer executes before every return path:
fn compute(x: int): int
defer
println("Cleanup")
end defer
if x < 0
return -1 // defer runs here
end if
return x * 2 // defer runs here too
end compute
defer also works inside Active Object methods, including a void
exclusive/shared method with no explicit return — the deferred code
runs at the method's implicit end (previously silently
discarded).
11.3 Spawn Expression
The spawn expression creates a fire-and-forget concurrent task.
The spawned function runs in a separate thread.
proc background_task()
println("Running in background")
end background_task
proc main()
spawn background_task() // Runs asynchronously
println("Main continues immediately")
end main
Note: spawn is for simple fire-and-forget tasks. For stateful concurrency with synchronization, use Active Objects (see Section 8).
| Feature | spawn | Active Objects |
|---|---|---|
| State | Stateless | Stateful |
| Synchronization | None | Automatic |
| Return value | None | Methods can return |
| Use case | Background work | Concurrent objects |
11.4 The panic() Function
panic(message) is a noreturn builtin: it prints message and aborts the
process immediately with a non-zero exit status. Unlike Result/Option, a
panic cannot be caught or recovered from — control never returns to the
caller. It's what option.unwrap and result.unwrap_ok/unwrap_err call
internally when handed the wrong variant (see §11.1 above).
Reserve panic for programmer errors and invariant violations ("this should
never happen"); use Result[T, E] for conditions a caller is expected to
handle. See reference/035_ERROR_HANDLING.md,
"The panic() Function", for exit-code conventions and the fn main(): int
pattern.
fn divide(a: int, b: int): int
if b == 0
panic("division by zero")
end if
return a / b
end divide
proc main()
println("10 / 2 = ${divide(10, 2)}")
end main
12. Memory Management
Reef uses automatic garbage collection for memory safety. The GC (reef_heaps.c in the runtime) is:
- Mark-and-sweep, with lazy sweeping
- Precise, not conservative: stack roots come solely from the compiler-generated shadow-frame chain, not a scan of raw stack bytes
- Cooperative stop-the-world during the mark phase (all Active Object threads reach a safepoint before marking proceeds)
See docs/runtime/ for the full heap-block layout and collector internals; this is implementation detail, not part of the language surface.
// Objects are allocated and freed automatically
mut obj = new MyObject()
// ... use obj ...
// obj is freed when no longer reachable
13. Unsafe Code
Reef provides unsafe blocks for low-level operations that require bypassing the type system's safety guarantees. Unsafe code is necessary for FFI (Foreign Function Interface) interoperability with C libraries.
13.1 Unsafe Blocks
calloc/malloc/free are not bare builtins — they come from sys.platform.libc (or an extern "C" declaration you write yourself; see reference/105_FFI.md):
import sys.platform.libc as libc
proc main()
// Normal safe code here
unsafe
// Low-level operations allowed here
let ptr: pointer = libc.calloc(100, 1)
let buf: [byte] = ptr // Pointer to array coercion
buf[0] = 65
println("First byte: ${buf[0]}")
libc.free(ptr)
end unsafe
// Back to safe code
end main
13.2 Unsafe Modules
An entire module can be declared unsafe, which suppresses bounds checking and enables unsafe type coercions for all code within the module:
unsafe module mymodule
proc fast_copy(dst: [byte], src: [byte], len: int)
// No bounds checks on array access — entire module is unsafe
for i in 0 to len
dst[i] = src[i]
end for
end fast_copy
end module
This is equivalent to wrapping the entire module body in an unsafe block, but more convenient for performance-critical modules.
13.3 Unsafe Block Effects
Inside unsafe blocks (or unsafe modules):
- Array and string bounds checking is suppressed (no
reef_array_check/reef_string_checkcalls) - Pointer operations and type coercions are allowed (see below)
The following operations are only allowed inside unsafe blocks:
Pointer Operations
unsafe
let ptr: pointer = calloc(100, 1)
let buf: [byte] = ptr // pointer -> array coercion
some_c_function(buf) // array -> pointer coercion (for FFI)
end unsafe
The nil Literal
unsafe
let ptr: pointer = nil // Null pointer
if ptr == nil
println("Pointer is null")
end if
end unsafe
Type Coercions
Within unsafe blocks, the following type coercions are allowed:
| From | To | Notes |
|---|---|---|
pointer |
[T] |
FFI allocation results |
[T] |
pointer |
Passing arrays to FFI functions |
string |
pointer |
String to C pointer |
pointer |
string |
C pointer to string |
int |
uint8, int8, uint16, int16 |
Integer narrowing |
uint8, int8, uint16, int16 |
int |
Integer widening |
char |
int |
Character to code point |
int |
char |
Code point to character |
Note: the pairs above are implicit coercions — they apply to a plain
assignment (let x: T = value) with no as, and only these specific pairs
are recognized. Integer-address casts (int/uint64 ↔ pointer/[T])
are a different, narrower mechanism — see §13.3a below — and are not
in this implicit-coercion set: let p: pointer = some_uint64 is rejected
even inside unsafe; it requires the explicit as form,
some_uint64 as pointer.
13.3a Raw Address Casts
Reef 0.7.8 restored explicit as casts between integer types and
pointer/[T], but only inside unsafe blocks. This is the
idiom bare-metal and MMIO (memory-mapped I/O) code relies on: a raw numeric
address, cast to a pointer or to an array type so it can be read and
written like memory. It restores behavior that existed before 0.5.20 tightened
cast strictness, and it is a distinct mechanism from the implicit-coercion
table above — this is the as cast operator, applied to a plain integer,
not an assignment coercion.
let addr: uint64 = 4096u64
let p = addr as pointer // Type Error outside unsafe:
// "integer to pointer cast requires unsafe"
Outside unsafe, casting an integer to pointer or to an array type is
still rejected — the restriction from 0.5.20 remains the default. Inside
unsafe, both directions are legal again:
unsafe
let addr: uint64 = some_array as pointer as uint64
let p = addr as pointer // integer -> pointer
let view = addr as [int] // integer -> [T]
end unsafe
Use uint64 (or the target's native word size) as the address type, not
int — Reef's int is a 32-bit C int by design (§4.2), so round-tripping
a 64-bit host address through int truncates it and produces an invalid
pointer.
See 100_UNSAFE.md
for a full worked MMIO example (volatile_read32/volatile_store32 through
an integer-derived pointer) and examples/test_unsafe_ptr_cast.reef for the
compiler's regression test of this cast pair.
13.4 FFI with Byte Arrays
The byte type (alias for uint8) is particularly useful for FFI:
import sys.platform.libc as libc
fn build_greeting(): [byte]
unsafe
// Allocate a byte buffer
let buf: [byte] = libc.calloc(6, 1)
// Fill it with data
buf[0] = 72 // 'H'
buf[1] = 101 // 'e'
buf[2] = 108 // 'l'
buf[3] = 108 // 'l'
buf[4] = 111 // 'o'
return buf
end unsafe
end build_greeting
proc main()
let greeting = build_greeting()
unsafe
println("First byte: ${greeting[0]}")
end unsafe
end main
13.5 Safety Guidelines
When using unsafe code:
- Minimize unsafe scope - Keep unsafe blocks as small as possible
- Validate inputs - Check pointers for nil, validate array bounds
- Document assumptions - Comment what invariants must hold
- Encapsulate - Wrap unsafe code in safe APIs when possible
- Test thoroughly - Unsafe code can cause crashes and memory corruption
14. Documentation Generation
14.1 Overview
Reef provides built-in documentation generation through the reefc doc command. Documentation is extracted from source code comments and converted to markdown (or HTML) API reference documentation.
14.2 Command Usage
# Generate docs for current project
reefc doc
# Specify output directory
reefc doc --output docs/api
# Include private items
reefc doc --private
# Verbose output
reefc doc -v
# Show help
reefc doc --help
14.3 Configuration
Add a [docs] section to reef.toml:
[package]
name = "mylib"
version = "1.0.0"
[docs]
output = "docs/api" # Output directory (default: docs/)
title = "MyLib API Documentation" # Documentation title
include_private = false # Include private items (default: false)
source_url = "https://github.com/user/mylib" # Source code URL
14.4 Generated Output Structure
docs/
├── index.md # Landing page with module overview
├── modules/
│ ├── io.console.md # Module documentation
│ └── collections.list.md
├── types/
│ ├── Counter.md # Type documentation
│ └── List.md
└── functions/
└── println.md # Standalone function docs
14.5 Documentation Best Practices
For Modules:
// Module: io.console
//
// Console input/output operations for Reef programs.
//
// This module provides thread-safe functions for reading from
// standard input and writing to standard output/error streams.
//
// ## Example
// ```reef
// import io.console
//
// println("Hello, world!") // println is a global builtin, no import needed
// match console.readLine()
// Option_Some(name) => println("Hello, ${name}!")
// Option_None() => println("No input")
// end match
// ```
For Active Objects:
// A thread-safe counter using Active Objects.
//
// Counter provides automatic synchronization for all operations,
// eliminating the need for manual locking.
//
// ## Example
// ```reef
// let counter = new Counter()
// counter.increment()
// println("Value: ${counter.get()}")
// ```
//
// ## Thread Safety
// All methods are `exclusive` and automatically synchronized.
// Multiple threads can safely access the same Counter instance.
active object Counter
value: int
// Initializes the counter to zero.
init()
self.value = 0
end init
// Increments the counter by 1.
//
// This method is thread-safe and can be called concurrently
// from multiple threads.
exclusive proc increment()
self.value = self.value + 1
end increment
// Returns the current counter value.
//
// ## Returns
// The current value of the counter
exclusive fn get(): int
return self.value
end get
end Counter
For Functions:
// Divides two integers with overflow checking.
//
// ## Parameters
// - `a`: The dividend
// - `b`: The divisor (must not be zero)
//
// ## Returns
// The quotient of a divided by b
//
// ## Panics
// Panics if `b` is zero -- `panic(message)` is a noreturn builtin that
// aborts the program (see [reference/035_ERROR_HANDLING.md](reference/035_ERROR_HANDLING.md), "The panic() Function").
//
// ## Example
// ```reef
// let result = divide(10, 2) // Returns 5
// ```
fn divide(a: int, b: int): int
if b == 0
panic("division by zero")
end if
a / b
end divide
15. Standard Library Overview
See full specification for complete standard library documentation.
15.0 Filesystem Modules (v0.5.0)
The fs.* module family provides native filesystem operations, replacing common shell-out patterns:
fs.stat— File type checks (is_file,is_directory,is_symlink,exists) and metadata (file_size,file_mode,file_uid,file_gid,file_mtime)fs.perm— Permission modification (chmod,chown,lchown), convenience helpers (set_executable,set_readonly), access tests (is_readable,is_writable,is_executable), POSIX constants (S_IRUSRthroughS_ISVTX)fs.link— Symlink operations (symlink,readlink), hard links (hardlink)fs.ops— File copy (copy_file,copy_file_preserve,copy_symlink), removal (remove_file,remove_tree), rename (rename), recursive tree operations (copy_tree)
These complement existing modules io.file (content read/write) and io.dir (directory create/list). All functions delegate to C runtime reef_fs.c via the FFI hub at sys.platform.runtime.
16. Example Programs
16.1 Hello World
println is a global builtin — no import needed.
module main
proc main()
println("Hello, World!")
end main
end module
16.2 Age Calculator
io.readLine() (no such module) doesn't exist; console input is io.console's readLine(), returning Option[string]. Parsing is core.str.to_int, returning Result[int, core.error.Error] — not a fictional ParseError. Because console.readLine() blocks on an interactive terminal (it isn't exercised by any automated test — see reef-compiler/examples/test_console_auto.reef), the compiled version below drives the same parsing/branching logic with a fixed string instead of live stdin; the commented lines show how it wires up to real input.
module main
import time.time
import core.str as str
import core.option as option
import core.result as result
fn calculate_age(birth_year: int): int
return time.time_year(time.time_now()) - birth_year
end calculate_age
proc print_age(age: int)
if age < 0
println("Error: Birth year cannot be in the future!")
elif age == 0
println("You were born this year! Welcome to the world!")
elif age == 1
println("You are 1 year old.")
else
println("You are ${age} years old.")
end if
end print_age
proc handle_input(input: string)
match str.to_int(input)
Result_Ok(year) =>
print_age(calculate_age(year))
end
Result_Err(_) =>
println("Error: Invalid year entered.")
end
end match
end handle_input
proc main()
println("=== Age Calculator ===")
// Interactively: print("Enter your birth year: ")
// match console.readLine()
// Option_Some(input) => handle_input(input) end
// Option_None() => println("Error: No input.") end
// end match
handle_input("1990")
end main
end module
16.3 Concurrent Counter
Reef has no fmt module — println is a global builtin. There is no bare sleep()/yield(); the timed wait below is time.clock.sleep_millis. spawn cannot take a closure literal (spawn proc() ... end is a syntax error) or a function with arguments and run truly concurrently (see reference/070_SPAWN.md), so this version demonstrates concurrent access the way Active Objects actually provide it: the object's own run() thread and the caller's thread both call increment() on the same instance at once, and the exclusive lock keeps the result correct:
module main
import time.clock as clock
active object Counter
value: int
init()
self.value = 0
end init
exclusive proc increment()
self.value += 1
end increment
exclusive fn get(): int
return self.value
end get
// Active body -- increments concurrently with the foreground thread in main()
run()
for i in 0 to 1000
self.increment()
end for
end run
end Counter
proc main()
let counter = new Counter()
// Foreground thread increments too, concurrently with run()
for i in 0 to 1000
counter.increment()
end for
clock.sleep_millis(200) // let run() finish its increments
println("Final count: ${counter.get()}") // 2000 once both threads finish
end main
end module
17. Inline Assembly
Reef supports inline assembly for low-level hardware access, performance-critical code, and OS/embedded development. Assembly functions are defined at the module level and generate native machine code for the target architecture.
17.1 Syntax
Assembly Procedure (No Return Value)
asm proc name(param1: type1, param2: type2) for architecture
// Assembly instructions
end name
Assembly Function (With Return Value)
asm fn name(param1: type1, param2: type2): return_type for architecture
// Assembly instructions
MOV result, value // Use 'result' for return value
end name
17.2 Supported Architectures
| Architecture | Syntax Style | Target Flag |
|---|---|---|
amd64 |
Intel syntax | --target amd64 or --target amd64-baremetal |
arm64 |
ARM syntax | --target arm64 or --target arm64-baremetal |
17.3 Examples
x86-64 (AMD64)
// Halt CPU - wait for interrupt
asm proc hlt() for amd64
HLT
end hlt
// Read from I/O port
asm fn inb(port: int): int for amd64
MOV EDX, port
XOR EAX, EAX
IN AL, DX
MOV result, EAX
end inb
// Write to I/O port
asm proc outb(port: int, value: int) for amd64
MOV EDX, port
MOV EAX, value
OUT DX, AL
end outb
// Atomic compare-and-swap
asm fn cas(ptr: pointer, old_val: int, new_val: int): int for amd64
MOV RAX, old_val
MOV RCX, new_val
MOV RDX, ptr
LOCK CMPXCHG [RDX], ECX
MOV result, EAX
end cas
ARM64 (AArch64)
// Wait for interrupt
asm proc wfi() for arm64
WFI
end wfi
// Data memory barrier
asm proc dmb() for arm64
DMB SY
end dmb
// Read system register (timer)
asm fn read_timer(): int for arm64
MRS X0, CNTPCT_EL0
MOV result, X0
end read_timer
17.4 Parameter Access
Parameters are accessed by name within assembly code:
asm fn add_values(a: int, b: int): int for amd64
MOV EAX, a // Load parameter 'a'
ADD EAX, b // Add parameter 'b'
MOV result, EAX // Store in 'result'
end add_values
The compiler substitutes parameter names with appropriate register/memory operands.
17.5 Return Values
For asm fn (functions with return values):
- Use
resultas the destination for the return value - The compiler handles moving
resultto the appropriate return register
asm fn get_flags(): int for amd64
PUSHFQ
POP RAX
MOV result, RAX
end get_flags
17.6 Multi-Architecture Support
Define the same function for multiple architectures:
// AMD64 version
asm proc memory_barrier() for amd64
MFENCE
end memory_barrier
// ARM64 version
asm proc memory_barrier() for arm64
DMB SY
end memory_barrier
The compiler selects the appropriate version based on --target.
17.7 Baremetal Compilation
For OS kernels and embedded systems, use baremetal targets:
reefc kernel.reef --target amd64-baremetal --entry none --emit-c
Baremetal Flags
| Flag | Description |
|---|---|
--target amd64-baremetal |
x86-64 freestanding mode |
--target arm64-baremetal |
ARM64 freestanding mode |
--target riscv64-baremetal |
RISC-V 64-bit freestanding mode |
--no-stdlib |
Skip libc linkage |
--entry <name> |
Custom entry point name |
--entry none |
Don't generate entry point |
--linker-script <path> |
Custom linker script |
Minimal Kernel Example
// kernel.reef - Minimal x86-64 kernel
asm proc hlt() for amd64
HLT
end hlt
asm proc cli() for amd64
CLI
end cli
extern "C" proc reef_putchar(c: char)
proc print_string(s: string)
mut i = 0
// Reading s[i] through the NUL terminator requires unsafe -- the
// checked form panics on the index == length read this idiom needs
// (same idiom as core.str.length; reference/110_INLINE_ASSEMBLY.md).
unsafe
mut ch = s[i]
while ch != '\0'
reef_putchar(ch)
i = i + 1
ch = s[i]
end while
end unsafe
end print_string
proc main()
cli()
print_string("Hello from Reef OS!")
loop
hlt()
end loop
end main
17.8 Restrictions
- Module Level Only: Assembly functions must be declared at module level
- No Active Objects: Cannot define
asm fninside Active Object definitions - Simple Types: Parameters must be simple types (int, pointer, char, etc.)
- Single Architecture: Each
asm fn/proctargets one architecture - No Closures: Assembly functions cannot capture variables
17.9 Generated Code
Reef generates GCC-compatible inline assembly:
asm fn add(a: int, b: int): int for amd64
MOV EAX, a
ADD EAX, b
MOV result, EAX
end add
Generates:
int reef_add(int a, int b) {
int __result;
__asm__ __volatile__ (
".intel_syntax noprefix\n"
"MOV EAX, %[a]\n"
"ADD EAX, %[b]\n"
"MOV %[result], EAX\n"
".att_syntax\n"
: [result] "=r" (__result)
: [a] "r" (a), [b] "r" (b)
: "memory", "cc"
);
return __result;
}
18. Syntax Summary
Block Delimiters
All blocks use end keyword:
// Functions
proc name()
// body
end name // Named end (recommended)
// Or
proc name()
// body
end fn // Keyword end (allowed)
// Control flow
if condition
// body
end if
while condition
// body
end while
for item in collection
// body
end for
loop
// body
end loop
match value
pattern =>
// body
end
end match
// Types
type Name = struct
// fields
end Name
active object Name
// body
end Name
// Modules
module name
// body
end module
Labeling Rules
-
Named constructs (functions, types, objects, modules):
- Preferred:
end ConstructName - Allowed:
end keyword(e.g.,end fn,end object)
- Preferred:
-
Control flow (if, while, for, loop, match):
- Use keyword:
end if,end while,end for,end loop,end match
- Use keyword:
-
Simple blocks:
- Can use just
endif unambiguous - But labeled ends are recommended for readability
- Can use just
No Whitespace Sensitivity
Indentation is style, not syntax — there is no statement-separator token (no semicolons); keywords like end delimit blocks, not newlines:
// This is valid (ugly but valid):
proc ugly() if true print("hi") end if end ugly
// This is also valid (good style):
proc clean()
if true
print("hi")
end if
end clean
The end keyword closes blocks, not indentation!
END OF SPECIFICATION