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
        };
    }