Global Variables

    Global variables are declared at the top-level scope. They are accessible throughout the module or package, depending on their visibility modifier.

    Visibility

    • Private: Declared without a modifier; accessible only within the current module.
    • Public (pub): Accessible from other modules or files.
    const VERSION = "1.0"; // private, immutable
    
    pub const COUNTER = 0; // public, immutable
    

    Mutability

    • const: Immutable after declaration. Attempting to reassign will cause a compile error.
    • var: Mutable; the value can be changed throughout the program lifecycle.
    const PI = 3.14159;
    
    PI = 3.14; // ERROR!
    

    External Symbols

    Use the extern keyword to declare symbols defined in external libraries or other translation units. These do not include an initializer.

    extern errno: int32; // private external symbol
    
    pub extern stdin: void*; // public external symbol
    

    Compile-Time Constants and Runtime Globals

    Cyrus distinguishes between compile-time constants and runtime global variables.

    • A const declaration at the top level creates a compile-time constant. Its value must be known at compile time and can be used in any compile-time expression, such as array sizes or type parameters.
    const MAX_SIZE = 1024;
    
    pub fn main() {
        var buffer: uint8[MAX_SIZE]; // OK: MAX_SIZE is const-evaluable
    }
    
    • A static declaration creates a runtime global variable that exists in the binary. Even if declared static const, the value is stored in the data section and is not available for compile-time evaluation.
    static const PAGE_SIZE: int32 = 4096;
    
    pub fn main() {
        var arr: int32[PAGE_SIZE]; // ERROR: PAGE_SIZE is a runtime global
    }
    

    You may use static var for mutable global state:

    static var counter: int32 = 0;
    
    pub fn increment() {
        counter += 1;
    }
    

    Important: Declaring a global variable with plain var (without const or static) is invalid:

    var foo = 0; // ERROR: global mutable variables must be declared with `static`
    

    This restriction prevents potential side effects that could arise during compile-time evaluation, keeping the language safe and predictable.

    Modifiers for Static Variables

    Static variables (both static const and static var) support several modifiers that control their linkage, visibility, placement, and other attributes. These modifiers appear before the static keyword.

    ModifierDescriptionPlatform
    weak

    Marks the symbol as weak, allowing multiple definitions without conflict.

    All
    dllimportImports the symbol from a dynamic-link library.Windows
    dllexport

    Exports the symbol for use in a dynamic-link library.

    Windows
    section("name")

    Places the variable in a specific section (e.g., .data, .bss).

    All
    placement

    (Reserved for future use) Specifies a custom memory placement.

    linkage

    (Reserved for future use) Controls linkage behavior.

    Examples:

    // Weak symbol (allows multiple definitions)
    weak static const fallback_value: int32 = 0;
    
    // Import from a DLL
    dllimport static var external_counter: int32 = 0;
    
    // Export to a DLL
    dllexport static var internal_state: int32 = 0;
    
    // Place in custom section
    section(".my_data") static var cache: int32 = 0;
    

    These modifiers are optional and can be combined where meaningful (e.g., weak dllexport static var ...), but their availability and behavior depend on the target platform.

    Local Variables

    Local variables are declared within function bodies using var or const. Their scope is restricted to the block in which they are defined.

    pub fn main() {
        var name = "Cyrus"; // mutable local
    
        const timeout_ms = 500; // immutable local
    }
    

    If you need to ensure a specific type, you can annotate the declaration:

    pub fn main() {
        const epsilon: float64 = 0.001;
    
        var retries: int32;
    }
    

    Zero Initialization

    In Cyrus, any variable declared without an explicit initial value is automatically zero-initialized.

    pub fn main() {
        var x: int32; // initialized to 0
        var y: float64; // initialized to 0.0
        var z: void*; // initialized to (nil)
    
        printf("%d\n", x);
    }
    

    Undefined Initialization

    In some cases, you may want to declare a variable without zero-initializing it. Cyrus provides the undefined keyword to explicitly indicate that a variable should remain uninitialized.

    pub fn main() {
        var x: int32 = undefined; // x is NOT zero-initialized
        var buffer: uint8[1024] = undefined; // buffer contains garbage data
    }
    

    This works for both local variables and static global variables:

    static var global_counter: int32 = undefined; // global variable without zero init
    

    Using undefined is useful for performance-critical code where you will immediately assign a value before reading, avoiding the cost of unnecessary zero-initialization.

    Important: Reading from an undefined variable before assigning a value results in undefined behavior. Use this feature carefully and only when you can guarantee the variable is set before use.

    pub fn main() {
        var x: int32 = undefined;
    
        // DO NOT read x here - undefined behavior!
        // printf("%d\n", x); // WRONG!
    
        x = 42; // Now it's safe to use
        printf("%d\n", x); // OK
    }
    

    No Late Type Inference

    Cyrus does not allow late type inference. Every variable must have its type known at the point of declaration, either:

    • Via an initializer (expression has a known type)
    • Via an explicit type annotation

    Declaring a variable without a type and without an initializer is not allowed, even if you assign to it later:

    var x; // ERROR!
    
    x = 10;
    

    You must either write:

    var x = 10; // type inferred as int32
    

    Or:

    var x: int32; // explicit type, zero-initialized to 0
    
    x = 10;