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.
- 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 moduleweak: weak linkagelinkonce: 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 inliningnoinline: forbid inliningalwaysinline: 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 returnsnounwind: function does not unwind the stackcold: unlikely to be executedhot: performance-critical pathoptsize: optimize for sizeoptnone: disable optimizationsnosanitize(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
printfat the ABI level, so it links against the realprintfsymbol 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));
}
Lambdas can be returned from functions or assigned to variables like any other type:
fn make_double_handler() fn(float64) float64 {
return fn(x: float64) float64 {
return x * 2.0;
};
}
pub fn main() {
const handler = make_double_handler();
const result = handler(15.3);
}
Global or local variables can also hold lambda definitions:
var handler = fn(x: float64, y: float64) float64 {
return 1.5;
};
pub fn main() {
const compute_handler = fn(cond: bool) fn(float64, float64) float64 {
if (cond) {
return fn(x: float64, y: float64) float64 {
return y + x;
};
} else {
return fn(x: float64, y: float64) float64 {
return -y * 2.0;
};
}
};
printf("%f ", (compute_handler(true))(2.0, 3.5)); // result: 5.500000
printf("%f ", (compute_handler(false))(2.0, 3.5)); // result: -7.000000
}
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
};
}
The main Entry Point
Every Cyrus program starts execution from a special entry-point function named main. This function is discovered by the linker at the ABI level and used as the process start symbol.
To ensure that main is visible as a symbol outside its defining module (so the linker can bind it as the program entry), it must be declared as a public function:
pub fn main() {
// program entry point
}
Parameter Constness
By default, function parameters in Cyrus are immutable. However, you can explicitly use the const keyword in the parameter list to enforce and document that the value of the argument cannot be modified within the function body.
fn sum(const x: int, const y: int) int {
x = 10; // ERROR!
return x + y;
}
- Immutability: When a parameter is marked const, any attempt to reassign its value inside the function will result in a compile-time error.
- Intent: This explicitly signals to both the compiler and other developers that the function treats these inputs as read-only.
- Optimization: Explicit constness allows the compiler to make stronger assumptions about the data flow, potentially leading to better register allocation and optimization.

