Functions
Functions in Cyrus are defined using the fn keyword, followed by the function name, parameter list, and return type. Functions are private by default and may be annotated with access specifiers to control visibility, linkage, and optimization behavior.
Basic Function Definition
A simple function that adds two integers:
fn sum(x: int, y: int) int {
return x + y;
}
Parameters are explicitly typed.
The return type appears after the parameter list.
return is required for non-void functions.
Recursion
Functions may call themselves recursively.
fn fibonacci(n: int) int {
if (n == 0) {
return 0;
} else if (n == 1) {
return 1;
} else {
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
pub fn main() {
fibonacci(10); // evaluates to 55
}
Recursion follows normal call semantics and stack behavior.
Function Modifiers
After defining a function's visibility and linkage (such as with extern), Cyrus allows functions to be further refined using function modifiers.
Function modifiers control how a function is:
- linked
- inlined
- called at the ABI level
- placed in the binary
- optimized or constrained by the compiler
Cyrus classifies function modifiers into distinct semantic groups. This separation is intentional: it keeps the language orthogonal and prevents ambiguous combinations.
Visibility
Controls where the function can be referenced from.
pub fn compute() int { ... }
If no visibility modifier is specified, the function is private to its module.
Linkage
Controls how the function symbol is resolved by the linker.
extern fn printf(fmt: const char*, ...) int32;
Additional linkage forms exist for advanced use cases:
- extern: symbol defined outside the module
- weak: weak linkage
- linkonce: link-once semantics (typically for deduplicated code)
Only one linkage modifier may be specified per function.
Inlining
Hints how the compiler should treat calls to the function.
inline fn add(a: int, b: int) int { ... }
Supported inlining modifiers:
- inline: prefer inlining
- noinline: forbid inlining
- alwaysinline: force inlining when possible
Inlining modifiers affect optimization but do not change semantics.
Prologue Control
Controls whether the compiler emits a function prologue and epilogue.
naked fn interrupt_handler() void { ... }
Naked emits no stack setup or teardown. This is intended for low-level code such as interrupt handlers or hand-written assembly bridges.
Export Modifiers
Control how the function is exported across binary boundaries.
pub dllexport fn api_entry() void { ... }
Supported export kinds:
- dllimport
- dllexport
These modifiers are platform-specific and primarily used when producing shared libraries.
Calling Convention
Specifies the ABI calling convention used when invoking the function.
callconv(fastcall) fn memcpy(dst: void*, src: void*, n: uint) void;
Supported calling conventions include:
- c
- system
- sysv64
- win64
- stdcall
- fastcall
- thiscall
- vectorcall
- interrupt
- naked
- aapcs
- cold
- fast
Only one calling convention may be specified per function.
Optional Function Flags
Optional flags provide additional semantic or optimization hints.
Multiple optional flags may be applied to the same function.
noreturn cold fn fatal_error(msg: char*) void { ... }
Supported optional flags include:
- noreturn: function never returns
- nounwind: function does not unwind the stack
- cold: unlikely to be executed
- hot: performance-critical path
- optsize: optimize for size
- optnone: disable optimizations
- nosanitize(name): disable a specific sanitizer
The compiler validates combinations to prevent contradictory flags.
Section Placement
Functions may be placed into specific binary sections.
section(".init") fn early_init() void { ... }
Multiple placement attributes may be specified when supported by the target.
This is typically used for kernel code, embedded systems, or fine-grained linker control.
Variadic Arguments
Cyrus supports two kinds of variadic arguments, allowing functions to accept a variable number of parameters.
C-style Variadic Arguments
C-style variadic functions use the ... notation, just like in C. These are typically used for interfacing with C libraries (e.g., printf) or when type safety is not a concern.
extern fn printf(format: const char*, ...) int;
fn main() {
printf("Hello %s, number = %d\n", "Cyrus", 42);
}
- Use this style primarily for compatibility with C and low-level APIs.
To access and iterate over the variable arguments from within your function, see the Builtins section in the documentation for details on @va_list, @va_start, @va_arg, and @va_end.
Function Name
Cyrus allows you to specify a different ABI-level (linker) name for a function than its Cyrus name. This is done using the as keyword after the declaration.
extern fn printf(fmt: string, ...) int as my_printf;
fn main() {
my_printf("Hello, %s!\n", "Cyrus");
}
- The function is declared as printf at the ABI level, so it links against the real printf symbol in the C standard library.
- Inside Cyrus, however, the function is referred to as my_printf.
- This allows you to avoid naming conflicts or to wrap existing library functions under more descriptive names.
Lambdas
Lambdas in Cyrus are anonymous functions. They look and behave like normal functions, but they don't need a name. You define them inline, assign them to variables, pass them around, or embed them inside structs.
Unlike closures in some other languages, Cyrus lambdas do not capture the surrounding environment. They are pure function objects defined with a clear, fixed signature.
Function Types
Every lambda has a function type, which describes its parameters and return type.
Example:
type HandlerFn = fn(int, int) void;
Defining a Lambda
The syntax is straightforward:
pub fn main() {
const handler = fn(x: int, y: int) int {
return x + y;
};
printf("%d\n", handler(5, 7));
}
Assigning to Variables
You can store lambdas in variables:
pub fn main() {
var add = fn(x: int, y: int) int {
return x + y;
};
printf("%d\n", add(2, 3)); // prints 5
}
No Environment Capture
In Cyrus, lambdas are not closures. That means they do not capture variables from their surrounding scope. For example:
fn main() {
var base = 10;
var adder = fn(x: int) int {
return x + base; // base is not accessible here
};
}
On this page

