Fun Language Reference
June 14, 2026 · View on GitHub
Language Features
Syntax & Structure
- Statically-typed, C-inspired: Variables and functions are statically typed; variables can be explicitly typed or inferred via
let. - Functions: Defined with
fun name(args) type { ... }. - Imports: Use
imp module;to import standard or user modules. Useimp module as alias;to import with a namespace alias. - Visibility: Prefix declarations with
pubto export them; declarations withoutpubare module-private. - Compounds: Custom types (like structs):
compound Point { num x; num y; }. - Quirks (Interfaces): Define required methods:
quirk Shape { area() num; }. - Implementations:
- Quirk implementation:
impl Rectangle as Shape { ... } - Plain compound methods:
impl Point { ... }
- Quirk implementation:
- Pattern Matching:
fit x { ... }for value-based branching. - Async/Await: Async functions are declared with
async fun ...; async calls must be awaited withawaitinside async functions. - Comments: Use
//for single-line comments.
Types
- Primitive Types:
num: Signed 64-bit integer (maps to Cint64_t)dec: 64-bit floating-point (IEEE double; maps to Cdouble)f32: 32-bit floating-point (maps to Cfloat)f64: 64-bit floating-point (maps to Cdouble)i8,i16,i32,i64: Signed fixed-width integersu8,u16,u32,u64: Unsigned fixed-width integersiN,uN: Arbitrary-width signed/unsigned integersstr: Stringbin: Booleanchr: Characterraw: Opaque/"void" type (useraw*for C-stylevoid*)
- Arrays:
num[] arr = [1, 2, 3]; - Pointers:
Node* next;(self-referential and forward-declared types supported) nil: The null pointer/string sentinel (a keyword; lowers to CNULL). It coerces to any pointer type and tostr, and compares with==/!=:num* p = nil;,if p == nil { ... },Node{next = nil}. No import needed (unlike the C macroNULL, which requiresimp std.c.def;).- Type Inference: Supported for variables via
let name = expr;(initializer required).
Type Inference (let)
letalways requires an initializer; the compiler infers the declared type from the expression.- Inference follows common literals and expressions:
- Numeric literals infer
numordecdepending on literal form. "text"infersstr,'c'inferschr,true/falseinfersbin.- Array literals infer
T[]from elements (for example[1, 2]->num[]). - Function calls infer the function's return type.
- Member access uses the receiver type (for example
Point p; let x = p.x;->num). - Indexing an array uses the element type (for example
let v = nums[i];->num).
- Numeric literals infer
- If the expression mixes numeric types, inference prefers the wider category (
decovernum).
Enums
- Declaration:
enum Color { Red, Green, Blue } - Longhand access:
Color.Red(works even if the enum is declared later in the file). - Shorthand access:
.Red(contextual; the expected enum type must be known from assignment, argument, orfit). - Assignments:
Color c = Color.Green;Color c = .Green;
- Function arguments:
fun takes(Color c) { ... }takes(.Blue);
- Pattern matching (
fit):fit c { .Red -> { ... }, .Green -> { ... }, _ -> { ... } }fit c { Color.Red -> { ... }, Color.Green -> { ... }, Color.Blue -> { ... } }
- Exhaustiveness: Missing enum variants in
fitmay emitfit_non_exhaustive; redundant branches or catch-alls may emitfit_unreachable_branch.
Data-carrying enums (sum types / tagged unions)
A variant may carry a positional payload, turning the enum into a tagged union (a payload-carrying sum type). An enum becomes a tagged union as soon as any variant has a payload; payload-free variants still coexist.
- Declaration:
enum Shape { Circle(num), Rect(num, num), Empty }- A payload is a parenthesized, comma-separated list of types:
Circle(num),Rect(num, num). Payloads can be primitives, compounds (by value), or monomorphized generic instances (Boxed(Box<num>)).
- A payload is a parenthesized, comma-separated list of types:
- Construction:
- Longhand:
Shape s = Shape.Circle(5);Shape r = Shape.Rect(3, 4); - Shorthand (expected type known):
Shape s = .Circle(5);ret .Some(x); - A payload-free variant is constructed like a normal enum value:
Shape e = Shape.Empty;
- Longhand:
- Pattern matching with destructuring: each arm binds the matched variant's
payload into locals visible in the arm body.
fit s { Shape.Circle(r) -> { ret r * r; } // r is the payload Shape.Rect(w, h) -> { ret w * h; } // w, h are the payload Shape.Empty -> { ret 0; } }- The shorthand arm form (
.Circle(r) -> ...) works too, and a_ -> { ... }catch-all covers the remaining variants.
- The shorthand arm form (
- Lowering: a tagged-union enum emits a C
struct { Enum_tag tag; union { ... } payload; }; payload-free (plain) enums keep the classic Cenumlowering for full backward compatibility. - Exhaustiveness: the same
fit_non_exhaustivecheck applies — cover every variant or add a_catch-all.
Control Flow
- If/Else: Standard conditional branching.
- Elif: Else-if chaining.
- Pattern Matching:
fitstatement for exhaustive and non-exhaustive matches. - Assert:
assert <condition>;aborts if condition is false. Optional message:assert <condition>, "msg";. Constant assertions may emitassert_constant. - Unreachable Statements: Statements after
ret,break, orcontinuemay emitunreachable_code. - For Loops:
- Range:
for i : 0..10 { ... } - Array:
for item : arr { ... } - Indexed:
for i, item :: arr { ... }(indexable sources only — arrays andVec) - Iterator:
for item : collection { ... }— drives any value whose type implements theIteratorquirk (next() Option<T>) or exposes aniter()returning one. Desugars to thenext()/Optionprotocol, soVec,Set, andMap(keys) iterate directly. - Map pairs:
for k, v :: map { ... }binds each key tokand its value tov. - While-style (condition):
for i < len { ... } - Infinite loop:
for true { ... } - Common in stdlib (for example
std/string.fn,std/net.fn, andstd/fs.fn).
- Range:
Defer
- Purpose: Run cleanup logic automatically when the current lexical scope exits.
- Order: LIFO (last
deferruns first). - Forms:
- Expression:
defer close(fd); - Block:
defer { log("done"); cleanup(); }
- Expression:
- Scope semantics:
- A
deferruns when the scope where it appears exits. - Function-scope defers run before
retand before implicit function end. - Loop-body defers run at the end of each iteration.
- On
continue/break, defers in the current loop iteration run before control leaves that iteration.
- A
Inline Assembly
- Block form:
asm { ... }; - String form:
asm "...";(use this for exact formatting/escapes) - Volatile:
asm volatile { ... };prevents reordering/elision - Architecture guard:
asm arch x86_64 { ... };(errors if target arch mismatches) - Operands & clobbers:
- Syntax:
asm volatile (out dst: "=r" = result; in src: "r" = value; clobber "rax", "memory") { ... };
- Reference named operands in templates using
%[name]. - Outputs are first, then inputs, then clobbers (GCC-style extended asm).
- Example (pass computed value):
num x = 21 + 21;num y = 0;asm volatile (out y: "=r" = y; in x: "r" = x) { mov %[x], %[y] };
- Example (pass computed value):
- Syntax:
- Notes:
- Asm block contents are preserved as raw text (including spaces, tabs, newlines, and comments).
- Fun does not validate assembly syntax inside asm blocks; correctness is decided by the downstream assembler/dialect (for example clang/GAS vs NASM differences).
- Use the string form when you need explicit escape control.
- Example (x86-style):
jmp $may fail under clang/GAS inline asm; label form like1: ... jmp 1bis typically more portable in that pipeline. - Supported arch names:
x86_64/amd64,x86/i386,aarch64/arm64,arm.
Functions
- Definition:
fun name(type arg, ...) return_type { ... } - Return: Use
ret value;to return from a function. - No Nested Functions: Functions cannot be declared inside other functions.
- Generic Functions:
fun id<T>(T x) T { ret x; }- Type arguments are inferred from call sites:
num v = id(1);.
- Type arguments are inferred from call sites:
- Default parameter values: A parameter may declare a default with
= expr; a call that omits it uses the default.fun greet(str name, num times = 1, str sep = ", ") { // implementation } greet("a"); // times = 1, sep = ", " greet("a", 3); // times = 3, sep = ", " greet("a", 3, "; "); // all explicit- Trailing only: defaulted parameters must come last — a required parameter cannot follow a defaulted one.
- Self-contained defaults: a default expression is evaluated at the call site,
so it may not reference
selfor an earlier parameter (use a constant, a global,nil, an enum variant, or another self-contained expression). Pointer defaults likenum* p = nilare allowed. - Works for free functions and methods (including generic,
async, and pointer-receiver methods).
Async / Await
- Async function declaration:
async fun name(args) type { ... } - Await usage:
awaitis valid only inside anasync funbody. - Required await: Calls to async functions and async quirk methods must be awaited.
- Await target: The awaited expression must resolve to an async call.
Example:
compound Counter {
num base;
}
quirk AsyncCounter {
async add(num x) num;
}
impl Counter as AsyncCounter {
async add(num x) num { ret self.base + x; }
}
async fun main() {
Counter c;
c.base = 41;
num out = await c.add(1);
_ = out;
}
Concurrency: virtual threads (fork) & channels
fork <call>;spawns a virtual thread — a fire-and-forget task that runs on a runtime M:N scheduler: a small pool of OS worker threads (sized to the CPU count) multiplexes many cheapforktasks. The target is anasync fun.forkreturns nothing; results flow back through channels.- Automatic drain:
mainblocks until everyforked task has completed before it returns, so spawned work always finishes. - Cooperative: a task that blocks on a channel op holds its worker (the yield points are the blocking primitives). It is not preemptive.
- Channel operators (sugar over
std.channel):ch <- v— sendvintoch(equivalent toch.send(v)).<-ch— receive fromch(equivalent toch.recv(), lossy; usech.recv_into(&out)for the error-aware form).- Note:
a < -b(a comparison against a negative) is unaffected — only the glued<-(no space) is the channel operator.
- Result-style send/recv (
std.channel): the low-levelrecv_into/sendreturn a numeric status code, but the ergonomic layer returns a value you canfiton:ch.recv_result()→RecvResult<T>with variantsOk(T),Closed,Timeout,Cancelled,Error(num)(alsorecv_result_timeout,recv_result_with_token,try_recv_result, and*_asyncvariants).ch.send_result(v)→SendResultwith variantsOk,Closed,Full,Cancelled,Error(num)(alsosend_result_timeout,try_send_result, async).fit ch.recv_result() { RecvResult.Ok(v) -> { // use v _ = v; } RecvResult.Closed -> { // drained } RecvResult.Timeout -> { // retry } RecvResult.Cancelled -> { } RecvResult.Error(e) -> { _ = e; } }
std.taskWaitGroup: wait for a batch offorked tasks.wait_group_new(n), each task callswg.done(), andwg.wait()blocks until allncomplete.
Example:
imp std.channel;
async fun square_into(Channel<num>* out, num v) {
out <- v * v;
}
fun main() num {
Channel<num> results = channel_new_cap(0, 8);
fork square_into(&results, 2);
fork square_into(&results, 3);
num total = (<-results) + (<-results);
ret total; // 4 + 9 = 13
}
Compounds & Quirks
- Compounds: Like C structs, can have methods via
impl. - Private fields (leading
_): A compound field whose name begins with an underscore is module-private — readable/writable only from code in the same module as the compound's declaration (including its ownimplmethods viaself._field). No keyword is needed; the leading_is the marker. Another module must go through public accessor methods. Example:compound Account { num id; // public num _balance; // private to this module } impl Account { pub balance() num { ret self._balance; } // self._balance is fine here } // In another module: `acc._balance` is rejected; `acc.balance()` works. - Quirks: Like interfaces/traits, define required methods.
- Impl: Attach methods to compounds or implement quirks for compounds.
- Method Dispatch: Quirk values can be used for dynamic dispatch (like trait objects).
- Quirk Method Visibility: Quirk methods follow normal visibility rules. Non-
pubmethods are callable inside the declaring module, but are not callable from importing modules. - Display Formatting (
{}): Formatting usesDisplay.to_string()only when that method is accessible at the call site. Ifto_string()is private in another module, formatting falls back to pointer-style output for that value.
Imports & Modularity
- Standard Library:
imp std.c.io;maps to C standard headers. - Relative Imports:
imp relative.parent;for user modules. - Import Alias:
imp mod1 as one;then call symbols asone.some_fn(). - Duplicate Export Collisions: Import modules that export the same public symbol by aliasing each module and calling through the alias namespace.
- Example:
imp mod1 as one;imp mod2 as two;num a = one.pick();num b = two.pick();
- Example:
- Circular Dependency Detection: Compiler detects and errors on circular imports.
C Interop
- C Macros: ALL_CAPS identifiers (e.g.,
NULL,INT_MAX) are allowed if the right header is imported. - Direct Mapping:
imp std.c.*;maps to C headers (stdio.h,limits.h, etc.). - Signature-only stdlib: Fun stdlib modules only declare signatures; C provides implementations.
- Printf formats:
numisint64_tin C. UsePRId64(from<inttypes.h>) or cast tolong longwith%lldwhen printing. - Compiler selection:
funuseszig ccby default. Override withFUN_CCand optionalFUN_CC_ARGS.
Standard Library Highlights
- std.io: file helpers +
print/println/print_num/print_dec/print_bin - std.sys: env access, process control (
sys_exit,sys_abort,sys_system), and PRNG wrappers - std.net: URL parsing, HTTP GET builder, and a best-effort local HTTP server launcher
- Note: std.net TCP/HTTP helpers use POSIX sockets via
std.c.net.
- Note: std.net TCP/HTTP helpers use POSIX sockets via
- std.option: Generic
Option<T>container withsome<T>/none<T>helpers. - std.result: Generic
Result<T>container withok<T>/err<T>helpers. - std.collections: Collection quirk helpers (len/is_empty).
Error Handling
- Type Checking: Errors for type mismatches, e.g., assigning
strtonum. - Undeclared Symbols: Errors for using undeclared variables or functions.
- Duplicate Declarations: Errors for redeclaring variables in the same scope.
- Missing Imports: Errors for importing non-existent modules.
- Incomplete Quirk Implementations: Errors if not all quirk methods are implemented.
Warning Controls
- Stable warning IDs:
-
return_local_ptr-fit_non_exhaustivefit_unreachable_branchunreachable_codeassert_constantunused_variable(with-warn-unused)unused_import(with-warn-unused)unused_function(with-warn-unused)unused_compound(with-warn-unused)missing_return— a non-voidfunction/method that can reach the end of its body without returning a value (the emitted C would return an indeterminate value). The analysis is conservative: a trailingret, an exhaustiveif/elif/elsewhere every branch returns, afitwith a default branch whose arms all return, and infinite loops (for {}/for true {}with nobreak) all count as returning. Always checked (not gated behind-warn-unused).
- Suppress next warning intentionally:
-
allow <warning_id>, "reason"; - Require next warning to appear:
-
expect <warning_id>, "reason"; - Scope:
allow/expectare statement directives. They work inside function bodies, andunused_variable,unused_import,unused_function, andunused_compoundmay also be controlled at module scope for the next top-level declaration or import. - Reason is required: the intent string documents why the warning is being allowed/expected.
- Behavior:
-
allowconsumes and suppresses the next emitted warning with that ID. -expectalso suppresses the next warning with that ID, but compilation fails if no such warning is emitted later.
Example:
fun bad() num* {
expect return_local_ptr, "tracked until allocator refactor";
num x = 1;
ret &x;
}
fun partial(bin x) {
allow fit_non_exhaustive, "legacy branch set, cleanup pending";
fit x {
true -> { }
}
}
fun main() {
partial(true);
num* p = bad();
_ = p;
}
See also:
- examples/advanced/warning_allow.fn
- examples/advanced/warning_expect.fn
- examples/advanced/return_local_ptr_allow.fn
- examples/advanced/unused_variable_warning.fn
- examples/advanced/unused_variable_allow.fn
- examples/advanced/unused_variable_expect.fn
- examples/advanced/unused_import_warning.fn
- examples/advanced/unused_import_allow.fn
- examples/advanced/unused_import_expect.fn
- examples/advanced/unused_function_warning.fn
- examples/advanced/unused_function_allow.fn
- examples/advanced/unused_function_expect.fn
- examples/advanced/unused_compound_warning.fn
- examples/advanced/unused_compound_allow.fn
- examples/advanced/unused_compound_expect.fn
- examples/advanced/fit_unreachable_branch_warning.fn
- examples/advanced/unreachable_code_warning.fn
- examples/advanced/assert_constant_warning.fn
- examples/error_cases/warning_expect_unmet.fn
Example
imp std.c.io;
compound Point { num x; num y; }
impl Point {
translate(num dx, num dy) {
self.x += dx;
self.y += dy;
}
}
fun main() {
Point p;
p.x = 1; p.y = 2;
p.translate(3, 4);
printf("p=(%d,%d)\n", p.x, p.y);
}
Additional Features
- Forward Declarations: Compounds can reference each other regardless of order.
- Self-referential Types: Supported via pointers.
- Pattern Matching: Exhaustive and non-exhaustive with
_default branch. - CLI Tool: Compile, transpile, and run Fun code from the command line.
See the examples directory for real code and advanced usage.