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 mut documented as unsafe for concurrent Active Object access

Recent Updates (v0.3.1 - December 8, 2025):

  • Module-Level Scope: const, let, mut declarations 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 fn and asm proc for 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):

  • byte type alias for uint8 (useful for FFI byte arrays)
  • ✅ Pointer ↔ array type coercions in unsafe blocks
  • ✅ Integer narrowing coercions in unsafe blocks (intuint8, 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 when clause

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) and fn (returns value)
  • Changed var to mut for mutable variables
  • Changed this to self for object self-reference
  • Added unless keyword for negative conditionals
  • String interpolation with "${expression}" syntax
  • nil restricted to unsafe blocks only (use Option[T] in safe code)
  • run is a keyword for Active Objects
  • Raw strings (r"...") have no escapes or interpolation
  • spawn is inline only (no block syntax)

Table of Contents

  1. Introduction
  2. Design Philosophy
  3. Lexical Structure
  4. Types and Values
  5. Expressions and Operators
  6. Statements and Control Flow
  7. Procedures and Functions
  8. Active Objects
  9. Modules and Packages
  10. Generics
  11. Error Handling and Concurrency Primitives
  12. Memory Management
  13. Unsafe Code
  14. Documentation Generation
  15. Standard Library Overview
  16. Interoperability
  17. Inline Assembly
  18. 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

  1. Safe Concurrency - Make concurrent programming as easy as sequential
  2. Performance - Native code generation with minimal overhead
  3. Reliability - Catch errors at compile time
  4. Productivity - Fast compilation, excellent tooling
  5. Clarity - Code should be obvious and maintainable

1.3 Syntax Philosophy

Reef uses end-based blocks inspired by Ruby and Crystal:

  • Blocks end with end keyword (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, and var are NOT keywords in Reef
  • proc for procedures (no return value), fn for functions (returns value)
  • asm for 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
  • defer for cleanup code at function exit, spawn for concurrent tasks
  • exit(code) built-in terminates program with specified exit code
  • mut for mutable variables, let for immutable
  • self for object self-reference
  • unless for negative conditionals
  • run is a keyword for Active Object bodies
  • typecase and inherited are reserved (0.9)
  • extends and override are contextual (special only in an object declaration)
  • Curly braces {} are available for special uses
  • Architecture names (amd64, arm64, riscv64) are contextual keywords, not reserved words — they're recognized after for in asm fn ... for <arch>, but are otherwise ordinary identifiers, usable as module path segments, variable names, or parameters.

⚠️ NOT IMPLEMENTED Keywords:

  • case, switch - Use match instead
  • interface, trait - Type constraints planned (see ROADMAP.md)
  • with - Context managers not implemented
  • yield - Generators not implemented
  • async is NOT a reserved word at all (it's usable as an ordinary identifier) — only await is 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 do
  • until - Do-until loop termination keyword
  • when - Match guards: _ when condition => (v1.4.0)
  • await - Blocks inside an exclusive/shared Active 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)
  • - - Difference
  • in - 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 yourselfcore.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 assignmentx, 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 then keyword after condition
  • Returns a value (can be assigned or used in expressions)
  • Must have else branch (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 nothing
  • fn: 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
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:

  1. Support reef.toml with required fields
  2. Use build/ directory for output by default
  3. Support reefc build command
  4. Support reefc --version and reefc --help

All Reef compiler implementations SHOULD:

  1. Support reefc run command
  2. Support reefc new and reefc init commands
  3. 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: float in export section
  • ✅ Generated as static const in 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: int in export section
  • ✅ Generated as static C 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 static C variable

Warning — Thread Safety: Module-level mut variables have no thread-safety guarantees. They must not be accessed from multiple Active Objects without external synchronization. Concurrent reads and writes to module-level mut state 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 where clause

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) or proc (no return)
  • The self parameter 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 to reef_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_check calls)
  • 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/uint64pointer/[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:

  1. Minimize unsafe scope - Keep unsafe blocks as small as possible
  2. Validate inputs - Check pointers for nil, validate array bounds
  3. Document assumptions - Comment what invariants must hold
  4. Encapsulate - Wrap unsafe code in safe APIs when possible
  5. 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_IRUSR through S_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 result as the destination for the return value
  • The compiler handles moving result to 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

  1. Module Level Only: Assembly functions must be declared at module level
  2. No Active Objects: Cannot define asm fn inside Active Object definitions
  3. Simple Types: Parameters must be simple types (int, pointer, char, etc.)
  4. Single Architecture: Each asm fn/proc targets one architecture
  5. 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

  1. Named constructs (functions, types, objects, modules):

    • Preferred: end ConstructName
    • Allowed: end keyword (e.g., end fn, end object)
  2. Control flow (if, while, for, loop, match):

    • Use keyword: end if, end while, end for, end loop, end match
  3. Simple blocks:

    • Can use just end if unambiguous
    • But labeled ends are recommended for readability

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