5.3. static
The static keyword (a contextual keyword that is a keyword only at the start of an item; the following mut is likewise a keyword only in the static mut position, 1.5.4) declares global memory that persists for the lifetime of the program.
static_decl ::= "static" "mut"? identifier ":" type_expr "=" const_expr ";"
extern_static ::= "static" "mut"? identifier ":" type_expr ";" // in extern block
5.3.1. Basic form
static SHARED_COUNTER: Atomic<i32> = Atomic::new(0i32);
static DEFAULT_CONFIG: Config = Config::default();
static APP_NAME: str = "rok";
The basic form is static, a name, : with a type, and = with an initializer. The type must be written, and the initializer must be a const expression in the sense of const, so it must be evaluable at compile time.
A static is reachable from every thread, so its type must be Sync (stdlib 2.13.9); this holds for lazily initialized statics too. static XS: List<i32> = []; is a compile error (collections, StrBuf and Cell<T> are !Sync); write static XS: Mutex<List<i32>> = Mutex::new([]);. Per-thread state uses thread::local (stdlib 3.4.4), whose ThreadLocal<T> is Sync. Statics of an extern block (5.3.10) are exempt.
5.3.2. Difference from const
const X: i32 = 42i32; // inlined at each use (no address)
static X: i32 = 42i32; // stable memory address (program lifetime)
| Aspect | const | static |
|---|---|---|
| Memory address | None | Stable address |
| At use site | Inlined | Memory reference |
| Lifetime | Compile time | Program lifetime |
| Mutability | Always immutable | Immutable by default (mutability via a wrapper) |
5.3.3. Permitted positions
static may be declared at module level or in a block (3.9.1), not in a trait or an impl (5.3.8).
static APP_NAME: str = "rok"; // module level - OK
fn f() -> i32 {
static CALLS: Atomic<i32> = Atomic::new(0i32); // OK - one slot, visible in this block
CALLS.fetch_add(1i32)
}
A static in a block is one slot for the whole program, initialized once, whatever instance of its function runs.
5.3.4. Mutable globals in ordinary user code: use a wrapper
static COUNTER: Atomic<i32> = Atomic::new(0i32);
static CACHE: Mutex<Dict<str, Data>> = Mutex::new(Dict::new());
A global that needs interior mutability is declared wrapped in a concurrency-safe wrapper.
Atomic<T>: lock-free atomic operations (integers,bool, pointers).Mutex<T>: lock-based protection.RwLock<T>: read-write lock, for aTthat is itselfSync(stdlib 2.16.4).
Cell<T> is single-threaded and !Sync, so it cannot be a static; a per-thread mutable global is thread::local(..).
All are standard library types; their exact signatures are defined in the library specification.
5.3.5. static mut: FFI only
Only for interfacing directly with the C ABI, static mut may be used, and only inside an extern "C" block.
extern "C" {
static errno: i32; // FFI global (read-only)
static mut atexit_count: u64; // FFI mutable global
}
static mutis allowed only inside anextern "C"block.static mutin ordinary rok code is an ERROR.- Assigning to a
staticthat is not an externstatic mutis a compile error (COUNTER = ...). Interior mutation goes through the methods of the wrappers above.
Shared mutable state is a direct cause of data races, hence this restriction. Ordinary code gets safety through wrappers; raw access is allowed only in the FFI domain.
5.3.6. Initialization order
Initialization order among multiple static declarations:
- Within the same module, declaration order.
- Across modules, dependency order: if one
static's initializer references anotherstatic, the latter is initialized first. - A circular dependency is a compile error.
The ordering check for const initializers is performed at compile time. Non-const initializers are initialized at first access. Initialization is thread-safe; the initializer runs exactly once, and other threads accessing concurrently wait until it finishes. An initializer that reads its own static panics.
5.3.7. Visibility
Like other items, a static may carry a visibility keyword.
pub static VERSION: str = "1.0.0";
pub(pkg) static INTERNAL: i32 = 42i32;
static _local: bool = false;
The detailed visibility rules are defined in visibility.
5.3.8. Combination with methods / traits
A static is a slot holding a value, not an item definer. It therefore does not appear in trait or impl block definitions; const or associated functions go there instead.
impl Counter {
static GLOBAL: i32 = 0i32; // ERROR - no static inside impl
const ZERO: i32 = 0i32; // OK - associated const
}
5.3.9. const restriction on initializers
If the initializer is a constant expression (5.2.4), it is initialized at compile time. Any other initializer is initialized once on first use (lazy initialization). Lazy initialization is thread-safe, and threads accessing concurrently wait until initialization finishes. Functions in the initializer need not be const fn (Mutex::new, etc.).
static PI: f64 = 3.14159f64; // const - compile time
static START: Instant = Instant::now(); // initialized once at first access
static CACHE: Mutex<Dict<str, Data>> = Mutex::new(Dict::new());
5.3.10. extern static: FFI
A static in an extern "C" block, as shown above, references from rok a global defined externally; no initializer is written.
extern "C" {
static errno: i32; // externally defined - read-only from rok
}
The detailed rules are defined in extern blocks.
5.3.11. Evaluation / memory model
The memory of a static persists until termination (const initializers are initialized at startup, others at first access). Every static is registered as a GC root at program startup (rok_gc_add_root, 8.5), so the GC keeps objects referenced by a static alive.
GC and runtime behavior are covered in 7. Runtime model and 8. Runtime ABI.