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
constdeclaration 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
staticdeclaration creates a runtime global variable that exists in the binary. Even if declaredstatic 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.
| Modifier | Description | Platform |
|---|---|---|
| weak | Marks the symbol as weak, allowing multiple definitions without conflict. | All |
| dllimport | Imports 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., | 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;

