Language Basics
Part of: Reef Language Reference Last reviewed on version: 0.9.0
Table of Contents
- Introduction
- Getting Started
- Lexical Structure
- Types
- Variables
- Operators
- Control Flow
- Functions and Procedures
- Structs and Records
- Enums and Pattern Matching
- Arrays
- Sets
- Subranges
Active Objects, Passive Objects, Generics, Modules, and FFI each have their own dedicated chapter — see 045_ACTIVE_OBJECTS.md, 040_OBJECTS.md, 055_GENERICS.md, 075_MODULES.md, and 105_FFI.md.
1. Introduction
What is Reef?
Reef is a modern, statically-typed systems programming language with language-level concurrency through Active Objects. Reef combines:
- Type safety from ML and Rust
- Clean syntax from Ruby and Crystal
- Concurrency from Active Oberon
- Systems performance from C
Key Features
- Active Objects: Language-level safe concurrency with automatic synchronization
- Passive objects: Classes with single inheritance and virtual methods (040_OBJECTS.md)
- Pattern Matching: First-class pattern matching with exhaustiveness checking
- Generics: Compile-time specialization (monomorphization)
- Set Types: Efficient bitsets from Pascal/Oberon tradition
- Memory Safety: Automatic garbage collection for heap-allocated values.
Raw pointers obtained inside
unsafeblocks are outside that guarantee. - Zero-Cost Abstractions: Compiles to efficient C code
When to Use Reef
Reef excels at:
- Concurrent systems programming
- Safe multi-threaded applications
- Systems requiring both safety and performance
- Projects benefiting from Active Object patterns
Reef is inspired by:
- Active Oberon (A2) - Active Objects, concurrency
- Modula-3 - Module system, safety
- Rust - Type safety, modern syntax
- Ruby/Crystal - Clean, readable syntax
- Pascal - Set types, subranges
2. Getting Started
Installation
See INSTALLATION.md in the main repository.
Requirements:
- reef-runtime (C library)
- OCaml and Dune (to build compiler)
- GCC or Clang (C11 support)
Your First Reef Program
proc main()
println("Hello, Reef!")
end main
Note: Every Reef program needs a
mainentry point. Useproc main()for simple programs, orfn main(): intwhen you need to return an exit code.
Compile and run:
reefc hello.reef
./hello
Output:
Hello, Reef!
Program Exit Codes
Programs can report success or failure to the operating system via exit codes.
Option 1: fn main(): int - Return an exit code from main:
fn main(): int
if something_failed
return 1 // Error
end if
return 0 // Success
end main
Option 2: exit(code) - Exit immediately from anywhere:
proc validate()
if invalid_input
println("Error: invalid input")
exit(1) // Terminate program immediately
end if
end validate
proc main()
validate() // May exit here
println("Valid!") // Only runs if validate() didn't exit
end main
Key points:
proc main()always exits with code 0fn main(): intreturns whatever you specifyexit(code)can be called from any function/procedure- Exit code 0 = success, non-zero = error
Check exit code in shell:
./myprogram && echo "Success" || echo "Failed"
echo $? # Shows the exit code
See Error Handling - Exit Codes for exit code conventions and more examples.
A Simple Function
fn add(a: int, b: int): int
return a + b
end add
proc main()
let result = add(5, 3)
print("5 + 3 = ")
print_int(result)
println("")
end main
Output:
5 + 3 = 8
3. Lexical Structure
Comments
// Single-line comment
/*
Multi-line comment
/* Nesting is supported */
*/
Identifiers
- Start with letter or underscore:
a-z A-Z _ - Continue with letters, digits, underscores:
a-z A-Z 0-9 _ - Case-sensitive
- Examples:
counter,_temp,MyType,value_2
Keywords
Reserved words (cannot be used as identifiers):
Control flow: if, else, elif, then, unless, when, while, for, in, to, step, loop, do, until, break, continue, match, defer, end
Functions: fn, Fn, proc, return, spawn
Variables: let, mut, const
Types: type, struct, record, enum, setof, subrange, byte, impl, is, trait, where
Active Objects: active, object, exclusive, shared, await, init, finalize, run, self
Passive objects (reserved since 0.9 Phase 6f — see the migration note in the CHANGELOG): typecase, inherited
Modules: import, export, module, as, ifarch
Primitive types: bool, char, int, string, float, int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64, size_t, pointer, Any
FFI: extern
Literals: true, false, new, nil
Special: unsafe, asm, to_map, type_id, type_name
Logical word forms: and, or, not (aliases for &&, ||, !)
This list (85 entries) is generated from the compiler's keyword table; using any of these as an identifier is a syntax error (the compiler names the word — see BUG-153).
Contextual keywords
Two words of the 0.9 passive-object surface are contextual, not
reserved: extends and override. Each has special meaning only in
its object-declaration position (object Button extends Widget,
override shared proc paint()) and remains an ordinary identifier
everywhere else — existing code using these names keeps compiling:
fn extends(override: int): int
return override + 1
end extends
proc main()
let answer = extends(2)
println("${answer}")
end main
typecase and inherited are different: they are reserved words
(0.9 Phase 6f, owner decision 2026-08-12 — a deliberate pre-1.0
breaking change). The contextual spellings were tried first and
measured: contextual typecase is structurally unparseable at
statement head, and contextual inherited could not represent
value-position base calls (let r = inherited area()). Reserving the
two words makes both forms ordinary grammar, at the standard cost —
using either word as an identifier is now a syntax error:
example.reef: Syntax error at line 2, column 17
note: 'inherited' is a reserved word and cannot be used as an identifier
(As with every reserved word, the reported location points at the token AFTER the identifier. See the CHANGELOG's 0.9 migration note; the 85-entry list above includes both words.)
What these four words do today
All four compile. Inheritance (extends / override / inherited),
typecase, finalize, and traits on objects are in 0.9 — see
040_OBJECTS.md. A stray override or inherited on a
class with no base is still a type error about your program:
| word | meaning |
|---|---|
extends |
one base class; methods are virtual |
override |
mandatory on a redefinition; rejected when nothing matches |
inherited |
static call to the defining class's base implementation |
typecase |
first-match runtime type dispatch; binder is the narrowed view |
extends and override remain ordinary identifiers outside those
positions (the fn extends(override: int) block above). typecase and
inherited cannot be identifiers.
The surface that is not in 0.9 is listed at the end of
040_OBJECTS.md (dyn Trait, generic objects,
object-typed spawn arguments, multiple inheritance).
Each word names one construct. A program that misuses one is diagnosed at that line rather than as a generic "objects" failure.
Block Terminators
All blocks use end with optional labels:
fn calculate(x: int): int
if x > 0
return x
end if
return 0
end calculate
Labeled ends (recommended):
fn calculate(x: int): int
if x > 0
return x
end if
return 0
end calculate
Rule: Named constructs (functions, types, objects) prefer end Name, control structures use end keyword.
4. Types
Primitive Types
Boolean
let flag: bool = true
let active: bool = false
Values: true, false
Integer Types
Default integer:
let count: int = 42 // 32-bit signed
Sized integers:
let small: int8 = 127
let medium: int16 = 32000
let standard: int32 = 1000000
let large: int64 = 9223372036854775807
let byte: uint8 = 255
let word: uint16 = 65535
let dword: uint32 = 4000000000
let qword: uint64 = 18446744073709551615
Integer literal suffixes:
let a = 127i8 // int8
let b = 255u8 // uint8
let c = 1000i16 // int16
let d = 65535u16 // uint16
let e = 1000000i32 // int32
let f = 1000000u32 // uint32
let g = 123456i64 // int64
let h = 999999u64 // uint64
Floating-Point Types
let pi: float = 3.14159 // 64-bit (default)
let small = 2.5 as float32 // 32-bit (float literals are `float`; cast to narrow)
let precise = 1.0 as float64 // 64-bit (float literals are `float`; cast to widen)
Character Type
let letter: char = 'A'
Note: Characters are Unicode code points (32-bit).
String Type
let message: string = "Hello, Reef!"
let path: string = r"C:\Users\name" // Raw string (no escapes)
Escape sequences:
\n- Newline\t- Tab\r- Carriage return\\- Backslash\"- Quote
System Types
size_t and pointer exist to describe values that cross the C boundary,
and that is where their values come from — an int literal is not
assignable to a size_t binding, and pointer values need an unsafe
context:
extern "C" fn strlen(s: string): size_t
extern "C" fn malloc(n: size_t): pointer
proc example()
let size: size_t = strlen("hello") // C size_t type
unsafe
let ptr: pointer = malloc(size) // C void* equivalent
end unsafe
end example
Composite Types
Arrays
Array types:
type IntArray = [int]
type Matrix = [[float64]] // 2D array
Array literals:
let numbers = [1, 2, 3, 4, 5]
let bools = [true, false, true]
let strings = ["hello", "world"]
Array operations:
let first = numbers[0] // Read
numbers[2] = 100 // Write
Example:
proc test_arrays()
let mut arr = [10, 20, 30]
arr[1] = 25
print("arr[1] = ")
print_int(arr[1])
println("")
end test_arrays
Output:
arr[1] = 25
Sets
Set types - Unordered collections of unique elements:
type Flags = setof[int]
type Permissions = setof[FileMode]
Set literals:
let s1: setof[int] = {1, 2, 3}
let s2: setof[int] = {0..9} // Range notation
let s3: setof[int] = {0, 2, 5..10} // Mixed
Set operations:
let union = s1 + s2 // Union
let intersection = s1 * s2 // Intersection
let difference = s1 - s2 // Difference
Membership test:
if 2 in s1
println("2 is in the set")
end if
Complete example:
proc test_sets()
let s1: setof[int] = {1, 2, 3}
let s2: setof[int] = {2, 3, 4}
if 2 in s1
println("2 is in s1")
end if
if !(5 in s1)
println("5 is not in s1")
end if
end test_sets
Output:
2 is in s1
5 is not in s1
Implementation: Sets are implemented as 32-bit bitsets (uint32_t), supporting elements 0-31.
Subranges
Subrange types - Restrict values to specific ranges:
type Digit = subrange 0 9
type Percentage = subrange 0 100
type Port = subrange 1 65535
Alternative syntax:
type DayOfMonth = 1..31 // Using .. notation
Usage:
type Digit = subrange 0 9
type Percentage = subrange 0 100
let age: Digit = 5
let score: Percentage = 75
Type compatibility: Subranges are compatible with int:
type Digit = subrange 0 9
proc test_subrange()
let d: Digit = 5 // int literal works
println("Subrange type works!")
end test_subrange
Output:
Subrange type works!
Implementation: Subranges are stored as int32_t with type compatibility checking.
5. Variables
Immutable Variables (let)
let name: type = value
let name = value // Type inferred
Example:
let x: int = 42
let message = "Hello" // Type inferred as string
let pi = 3.14159 // Type inferred as float
Mutable Variables (mut)
mut counter: int = 0
mut flag = true // Type inferred
Mutation:
counter = counter + 1
flag = false
Type Inference
Type annotations are optional when type can be inferred:
let x = 42 // Inferred as int
let s = "hello" // Inferred as string
let arr = [1, 2, 3] // Inferred as [int]
let obj = new Counter() // Inferred from constructor
When type annotation is required:
- Ambiguous expressions
- Empty collections
- Function parameters (always required)
- Explicit type desired for clarity
6. Operators
Arithmetic Operators
+ // Addition
- // Subtraction
* // Multiplication
/ // Division
% // Modulo
** // Power/exponentiation
Integer arithmetic:
let sum = 5 + 3 // 8
let diff = 10 - 4 // 6
let product = 6 * 7 // 42
let quotient = 15 / 3 // 5
let remainder = 17 % 5 // 2
Comparison Operators
== // Equal
!= // Not equal
< // Less than
> // Greater than
<= // Less or equal
>= // Greater or equal
Returns: bool
== / != apply to any two matching types (value equality for
numbers, strings, structs, enums; reference identity for objects and
Active Objects). Ordering (<, >, <=, >=) is defined only
for numeric types (integers including char and size_t, and floats).
string < string is not lexicographic — it is a type error (BUG-219).
Example:
if x == 5
println("x is 5")
end if
if score > 90
println("Excellent!")
end if
Logical Operators
Symbolic form:
&& // Logical AND
|| // Logical OR
! // Logical NOT
Word form (equivalent):
and // Logical AND
or // Logical OR
not // Logical NOT
Example:
if (x > 0) && (x < 100)
println("x is in range")
end if
if (x > 0) and (x < 100) // Same as above
println("x is in range")
end if
if !(flag) // Negation
println("flag is false")
end if
Set Operators
When operands are sets:
+ // Union
* // Intersection
- // Difference
in // Membership test
Example:
let s1: setof[int] = {1, 2, 3}
let s2: setof[int] = {2, 3, 4}
let union = s1 + s2 // {1, 2, 3, 4}
let common = s1 * s2 // {2, 3}
let unique = s1 - s2 // {1}
if 2 in s1 // true
println("Found!")
end if
Unary Operators
- // Numeric negation
! // Logical NOT
~ // Bitwise NOT (complement)
Example:
let x = 5
let neg = -x // -5
let flag = true
let inverted = !flag // false
Bitwise Operators
& // Bitwise AND
| // Bitwise OR
^ // Bitwise XOR
~ // Bitwise NOT
<< // Left shift
>> // Right shift
Example:
let a = 0b1100 // 12
let b = 0b1010 // 10
let and_result = a & b // 0b1000 = 8
let or_result = a | b // 0b1110 = 14
let xor_result = a ^ b // 0b0110 = 6
let shifted = a << 2 // 0b110000 = 48
Compound Assignment Operators
+= // Add and assign
-= // Subtract and assign
*= // Multiply and assign
/= // Divide and assign
%= // Modulo and assign
Example:
mut x = 10
x += 5 // x = 15
x -= 3 // x = 12
x *= 2 // x = 24
Operator Precedence
From highest to lowest:
- Postfix:
.,[],() - Unary:
-,!,~,not - Power:
**(right associative) - Multiplicative:
*,/,% - Additive:
+,- - Set membership:
in - Bitwise shift:
<<,>> - Comparison:
<,>,<=,>= - Type operators:
is,as - Equality:
==,!= - Bitwise AND:
& - Bitwise XOR:
^ - Bitwise OR:
| - Logical AND:
&&,and - Logical OR:
||,or
Use parentheses for clarity when precedence is unclear.
7. Control Flow
If Statement
if condition
statements
elif other_condition
statements
else
statements
end if
Example:
fn classify(n: int): string
if n < 0
return "negative"
elif n == 0
return "zero"
else
return "positive"
end if
end classify
If Expression (with then)
When you need a conditional that returns a value (like a ternary operator), use then:
let result = if condition then value1 else value2 end if
Key difference:
- If statement: Control flow, executes statements, no value returned
- If expression: Returns a value, requires
thenkeyword, must haveelse
Examples:
// Assign based on condition
let max = if a > b then a else b end if
// Use inline in expressions
println(if ready then "Yes" else "No" end if)
// Select between two values
let sign = if x < 0 then -1 else 1 end if
// With function calls
let name = if str.is_empty(input) then "default" else input end if
Note: If expressions must always have an else branch since they must return a value in all cases.
Unless (Inverted If)
unless condition
statements
end unless
Equivalent to: if !(condition)
Example:
unless ready
println("Not ready yet")
end unless
// Equivalent to:
if !ready
println("Not ready yet")
end if
While Loops
while condition
statements
end while
Example:
mut i = 0
while i < 10
print_int(i)
println("")
i = i + 1
end while
For Loops
for variable in start to stop
statements
end for
Example:
for i in 0 to 9
print_int(i)
println("")
end for
Note: Uses to keyword. The end is exclusive (0 to 9 loops 0-8).
With step:
for i in 0 to 100 step 10
print_int(i) // 0, 10, 20, ..., 90
end for
Note: The start/stop/step bounds must be simple values (a literal or a variable) — a call expression (e.g. for i in 0 to get_len()) is a syntax error; bind the result to a local first (let len = get_len()) and use that.
For-Each Loops
// Iterate over array elements
for item in collection
print_int(item)
end for
// With index variable
for i, item in collection
print_int(i) // 0, 1, 2, ...
print_int(item)
end for
Infinite Loops
loop
statements
if exit_condition
break
end if
end loop
Example:
mut count = 0
loop
count = count + 1
print_int(count)
println("")
if count >= 5
break
end if
end loop
Do-While and Do-Until
do while condition
statements
end do
do until condition
statements
end do
Both forms guarantee the body executes at least once. do while repeats while the condition is true; do until repeats until the condition becomes true.
Example:
mut count = 0
do while count < 5
count = count + 1
end do
// count is now 5
mut n = 0
do until n == 3
n = n + 1
end do
// n is now 3
Break and Continue
break // Exit innermost loop
continue // Skip to next iteration
Example:
for i in 0 to 9
if i == 5
continue // Skip 5
end if
if i == 8
break // Stop at 8
end if
print_int(i)
println("")
end for
8. Functions and Procedures
Functions (with return value)
fn name(param: type): return_type
statements
return value
end name
Example:
fn square(x: int): int
return x * x
end square
proc main()
let result = square(5)
print_int(result) // 25
println("")
end main
Procedures (no return value)
proc name(param: type)
statements
end name
Example:
proc greet(name: string)
print("Hello, ")
print(name)
println("!")
end greet
proc main()
greet("Alice")
end main
Output:
Hello, Alice!
Parameters
Type annotations required for all parameters:
fn add(a: int, b: int): int
return a + b
end add
Multiple parameters:
fn calculate(x: int, y: int, z: int): int
return x + y * z
end calculate
Return Statements
Functions must return a value:
fn get_value(): int
return 42
end get_value
Procedures can use return for early exit:
proc process(value: int)
if value < 0
return // Early exit
end if
println("Processing...")
end process
Labeled Ends
Functions and procedures support labeled ends:
fn calculate(x: int): int
return x * 2
end calculate
Both end calculate and end fn are valid.
9. Structs and Records
Declaring Structs
type Point = struct
x: int
y: int
end Point
Alternative (Pascal/Oberon style):
type Point = record
x: int
y: int
end Point
Note: struct and record are synonyms.
Creating Struct Instances
let p = new Point()
Accessing Fields
let p = new Point()
p.x = 10
p.y = 20
print_int(p.x) // 10
Structs with Methods
type Rectangle = struct
width: int
height: int
fn area(): int
return self.width * self.height
end area
end Rectangle
proc main()
let rect = new Rectangle()
rect.width = 5
rect.height = 10
let a = rect.area()
print_int(a)
println("")
end main
Output:
50
10. Enums and Pattern Matching
Declaring Enums
Simple enums:
type Color = enum
Red
Green
Blue
end Color
Enums with data (Sum types):
type Option = enum
Some(int)
None
end Option
type Result = enum
Ok(string)
Err(string)
end Result
Using Enums
Constructors are generated automatically:
let color = Color_Red()
let value = Option_Some(42)
let none = Option_None()
Note: Zero-argument constructors still need call parentheses — Color_Red
without () is a Type Error ("'Color_Red' is a function, not a variable"),
not a value. This applies in match patterns too: a bare Color_Red
pattern is REJECTED with a dedicated type error ("Bare enum constructor
pattern 'Color_Red' is not allowed — write 'Color_Red()' to match the
variant, or rename the binding") — because the same bare form used to
parse as a variable-binding pattern (a catch-all), which the exhaustiveness
checker trusted even though the code generated for that arm still tested
the real tag, so a match that's missing a variant elsewhere could be
wrongly accepted as exhaustive (no compile error) and crash at runtime if
that missing variant was ever matched against. Always
write Color_Red() — as an expression, a pattern, or a set-literal
element — so the checker sees the real constructor pattern.
Pattern Matching
match expression
pattern =>
statements
end
end match
match is for enums and other values. Runtime dispatch on a class
hierarchy is typecase — see
is / as / typecase.
Patterns supported:
- Wildcard:
_ - Variable:
name - Integer literal:
42 - Boolean literal:
true,false - String literal:
"hello" - Constructor:
Option_Some(x) - Constructor without binding:
Option_None()
Example with exhaustiveness:
type Option = enum
Some(int)
None
end Option
fn unwrap_or(opt: Option, default: int): int
match opt
Option_Some(_) =>
println("Has value")
return 42 // Simplified
end
Option_None() =>
println("No value")
return default
end
end match
end unwrap_or
Exhaustiveness Checking
The compiler enforces exhaustiveness - all cases must be covered:
Compile error (missing case):
fn bad_match(b: bool): int
match b
true => return 1 end
// ERROR: Missing case for 'false'
end match
end bad_match
Error message:
Type Error: Non-exhaustive match statement. Missing patterns:
false
Hint: Add missing cases or use '_' wildcard
Fixed with wildcard:
fn safe_match(n: int): string
match n
0 => return "zero" end
1 => return "one" end
_ => return "other" end // Catch-all
end match
end safe_match
Match Guards (when clause) ✅ NEW
Match guards add conditional logic to patterns using the when keyword. The guard is evaluated after the pattern matches, and the arm only executes if the guard is true.
Syntax:
match expression
pattern when condition =>
statements
end
end match
Example - Range checking:
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 guard:
fn in_range(n: int): bool
match n
_ when n >= 1 and n <= 10 =>
return true
end
_ =>
return false
end
end match
end in_range
Guards with external variables:
fn check_threshold(value: int, threshold: int): string
match value
_ when value > threshold =>
return "above"
end
_ when value == threshold =>
return "equal"
end
_ =>
return "below"
end
end match
end check_threshold
Note: Guards can reference both external variables (declared outside the match) and the current arm's pattern-bound variable, e.g. x when x != "" => .... The pattern binding is assigned before the guard runs, so it is safe to read there and in the arm body.
11. Arrays
Array Types
type IntArray = [int]
type StringArray = [string]
type BoolArray = [bool]
Array Literals
let numbers = [1, 2, 3, 4, 5]
let names = ["Alice", "Bob", "Charlie"]
let flags = [true, false, true]
Type inference: Element type inferred from first element.
Array Indexing
Reading:
let first = numbers[0]
let third = numbers[2]
Writing:
let arr = [1, 2, 3]
arr[0] = 100
arr[2] = 300
Complete Array Example
proc array_demo()
let arr = [1, 2, 3, 4, 5]
println("Original array:")
print_int(arr[0])
println("")
arr[0] = 100
println("After modification:")
print_int(arr[0])
println("")
end array_demo
Output:
Original array:
1
After modification:
100
Array Methods
Arrays have built-in methods that return new arrays (immutable style). The original array is not modified; reassign to update:
mut arr = [10, 20, 30]
// append(item) - returns new array with item added at end
arr = arr.append(40) // [10, 20, 30, 40]
// pop() - returns new array without last element
arr = arr.pop() // [10, 20, 30]
// remove(index) - returns new array without element at given index
arr = arr.remove(0) // [20, 30]
| 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 operations return a new array rather than mutating in place.
After reassignment the old array becomes unreachable and is reclaimed by the
collector eventually — but not at the point of reassignment. Reef sweeps lazily:
a collection marks live objects, and memory is reclaimed incrementally
afterwards, inside the allocator, when a later allocation needs a block. Building
an array by repeated push in a hot loop therefore produces garbage that is
reclaimed on someone else's schedule, not yours.
Multi-dimensional Arrays
type Matrix = [[int]]
let matrix = [[1, 2], [3, 4], [5, 6]]
let elem = matrix[0][1] // 2
12. Sets
Set Type Declaration
type IntSet = setof[int]
type CharSet = setof[char]
type ColorSet = setof[Color] // Set of enum values
Set Literals
Element enumeration:
let s: setof[int] = {1, 2, 3, 4}
Range notation:
let digits: setof[int] = {0..9}
Mixed:
let mixed: setof[int] = {0, 2, 5..10, 15}
Note: Empty set literals {} are not allowed (cannot infer type). Provide at least one element.
Set Operations
let s1: setof[int] = {1, 2, 3}
let s2: setof[int] = {2, 3, 4}
// Union - all elements in either set
let union = s1 + s2 // {1, 2, 3, 4}
// Intersection - elements in both sets
let common = s1 * s2 // {2, 3}
// Difference - elements in s1 but not s2
let unique = s1 - s2 // {1}
Membership Test
if element in set_value
println("Element is in set")
end if
Example:
let primes: setof[int] = {2, 3, 5, 7, 11}
if 7 in primes
println("7 is prime")
end if
if !(4 in primes)
println("4 is not prime")
end if
Output:
7 is prime
4 is not prime
Practical Set Example
type Permission = enum
Read
Write
Execute
end Permission
proc check_permissions()
let admin_perms: setof[Permission] = {Read, Write, Execute}
let user_perms: setof[Permission] = {Read, Write}
if Execute in admin_perms
println("Admin can execute")
end if
if !(Execute in user_perms)
println("User cannot execute")
end if
end check_permissions
Implementation Notes:
- Sets are value types (not heap-allocated)
- Implemented as bitsets (uint32_t)
- Efficient for small sets (0-31 elements)
- Suitable for flags, permissions, options
13. Subranges
Subrange Type Declaration
Keyword syntax:
type Digit = subrange 0 9
type Percentage = subrange 0 100
Range syntax (alternative):
type DayOfMonth = 1..31
type Hour = 0..23
Using Subrange Types
type Digit = subrange 0 9
proc test_digit()
let d: Digit = 5 // OK - within range
print_int(d)
println("")
end test_digit
Type Compatibility
Subranges are compatible with their base integer type:
type Port = subrange 1 65535
proc connect(port: Port)
println("Connecting...")
end connect
proc main()
let p: Port = 8080
connect(p) // Works - Port is compatible with int
end main
Use Cases
Bounded values:
type Index = subrange 0 9
proc main()
let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
let idx: Index = 5
// A subrange value cannot index an array directly — bind it to an int first.
let i: int = idx
print_int(arr[i])
println("")
end main
A subrange type cannot be used as an array index directly. arr[idx], where
idx is subrange-typed, is rejected:
Type Error: Array index must be int, got 'Index'
There is also no cast around it — idx as int is rejected in turn, because
casts require a heap-allocated target type. Assign the subrange value to an
int binding, as above.
Array and string indices otherwise accept the whole integer family, so an
int64 index needs no conversion:
proc main()
let arr = [10, 20, 30]
let i: int64 = 1i64
print_int(arr[i])
println("")
end main
Domain constraints:
type Age = subrange 0 150
type Score = subrange 0 100
type Month = subrange 1 12
Implementation: Stored as int32_t with type compatibility checking.
This covers the core language: lexical structure, types, variables, operators, control flow, functions, structs, enums, arrays, sets, and subranges. For Active Objects, generics, modules, and FFI, see the chapters linked in the table of contents above.
Previous: 010_PROJECT_STRUCTURE.md Next: 020_FUNCTIONS.md Index: 000_INDEX.md