Constants and Statics
Both const
(constants) and static
(static variables) are used to declare values that live for the entire duration of a program.
Declare Constants
Constants, declared using the const
keyword, are values that are inlined directly into the code wherever they are used. Think of them as symbolic names for literal values that are known at compile time.
- By convention,
const
names are in SCREAMING_SNAKE_CASE. - Constants are always immutable. You cannot declare a
const
mut
. - Unlike
let
bindings (where type inference is common), you must explicitly specify the type of aconst
. - Constants can be declared in any scope: While often found at the top level, constants can be declared inside functions, modules, or other blocks.
- The value of a
const
must be a constant expression, meaning it can be fully evaluated at compile time. This includes literals, arithmetic operations on literals, and calls to constant functions (which are declared asconst fn
instead offn
and are limited in what they can do). - The only lifetime allowed in a constant is
'static
, which is the lifetime that encompasses all others in a Rust program. const
values do not have a dedicated memory address. When you use a constant, the compiler essentially "copies and pastes" its value directly into the machine code at each usage site.
Constants are primarily used for values that truly never change throughout the lifetime of your program, such as mathematical constants or configuration values.
//! `const` Example. // Declare a constant in the global scope. // - The type must be provided. // - Constant names, like statics, should always be in SCREAMING_SNAKE_CASE. const LEN: usize = 5; fn main() { // Constants are essentially inlined wherever they are used, meaning that // they are bitwise copied wherever they are used. println!("The length is: {LEN}"); // The above is equivalent to: // println!("The maximum points allowed is: {10usize}"); // Constants are always immutable. // ERROR: LEN += 1; // Constants can be used in array definitions; in patterns; initializers of // statics & consts; enum discriminants; etc. let arr: [i32; LEN] = [0; LEN]; println!("arr: {arr:?}"); let x = 5; match x { LEN => { println!("x == LEN"); } // Use in a `match` pattern. _ => unreachable!(), } static MY_LEN: usize = LEN; // Use to initialize a `static`. // Use as a "const generic" parameter: struct Foo<const N: usize>([i32; N]); let _foo = Foo::<LEN>([0; 5]); // A constant's value can be calculated from an expression // at _compile_ time: const SECONDS_IN_ONE_HOUR: u32 = 60 * 60; // Of course, not all expressions can be evaluated at compile-time. // "Constant expressions" can only include literals, as above; // other constants; read-only statics; const parameters; and calls to // "const fn" functions. const C: usize = LEN + MY_LEN + std::mem::size_of::<u32>(); // `size_of` is `const fn`. // The following is prohibited, because the expression's value can only be // determined at runtime (strings are allocated on the heap): // ERROR: const MESSAGE: String = String::from("Hello"); // Despite the inlining, it is possible to obtain a reference to a // constant. The compiler either creates a temporary local variable or, if // possible, promotes it to global static memory (via a mechanism called // promotion / lifetime extension). Note that multiple references to the // same constant are _not_ guaranteed to refer to the same memory address. let refer: &'static usize = &LEN; println!("The length again: {refer}"); // Let's create a struct that implements a `Drop` destructor: struct PrintOnDrop(&'static str); impl Drop for PrintOnDrop { fn drop(&mut self) { // Print the inner string slice. println!("{0}", self.0); } } // Simply declaring a constant does not result in a call to `Drop`. const POD: PrintOnDrop = PrintOnDrop("This message does not appear."); // However, variables initialized with a constant follow normal `Drop` // behavior. const POD2: PrintOnDrop = PrintOnDrop("Dropped."); let _pod = POD2; let _pod1 = POD2; let _pod2 = &POD2; println!("Before drop."); // When each variable goes out of scope, its destructor is run. The above // prints `Dropped.` thrice. }
References
Declare Statics
Static variables (declared with static
) represent a single, fixed location in memory for the entire duration of the program. They are effectively global variables.
- By convention, static names are in SCREAMING_SNAKE_CASE.
- You must explicitly specify the type of the
static
in its declaration. static
variables must be initialized with a constant expression (made of literals, constants, calls toconst fn
functions...).- Static items have a consistent, fixed memory address throughout the program's execution.
- They have the
'static
lifetime, meaning they live for the entire duration of the program. - Unless it's a
static mut
(see below), the type of astatic
variable generally needs to implement theSync
trait, ensuring thread-safe access. This prevents unsynchronized access to data that could lead to corruption. Drop
behavior: static variables are not dropped when the program exits. Their memory is simply reclaimed by the operating system. If they hold resources that need explicit cleanup (like file handles), you'd typically use a runtime-initialized global likeonce_cell::sync::Lazy
orparking_lot::Mutex
.
//! `static` Example. // Statics are in SCREAMING_SNAKE_CASE, must declare their type, must be // initialized: static APPLICATION_NAME: &str = "My Awesome App"; static VERSION: u32 = 1; fn main() { println!("Welcome to {APPLICATION_NAME} v{VERSION}"); // Statics do not `Drop`. static POD: PrintOnDrop = PrintOnDrop("This message does not appear."); } // A struct that implements a `Drop` destructor. struct PrintOnDrop(&'static str); impl Drop for PrintOnDrop { fn drop(&mut self) { println!("{}", self.0); } }
Statics can be mutable (static mut
): Unlike const
, static variables can be declared as mutable using static mut
. However, accessing or modifying a static mut
variable is unsafe
, because it can lead to data races in a multi-threaded environment. Only consider static mut
in last resort.
//! `static mut` Example. //! //! `static mut` use is generally discouraged due to the potential for data //! races and undefined behavior. The following demonstrates alternatives. //! See <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/static-mut-references.html> use std::sync::Mutex; use std::sync::atomic::AtomicU16; use std::sync::atomic::Ordering; use std::thread; fn dont_do_this() { // Declare a mutable static variable: static mut VAL: i32 = 0; // An unsafe block is required when either reading or writing a mutable // static variable. Care should be taken to ensure that modifications to // a mutable static are safe with respect to other threads running in the // same process. unsafe { VAL += 1; // Single-threaded, thus OK. let v = VAL; assert_eq!(v, 1); } // Creating references to a mutable static generates compiler errors, // really limiting what you can do with a `static mut`. // BEWARE: In addition, statics can be initialized from mutable statics, // but they read the _initial_ value of that static. static READ_FROM_VAL: i32 = unsafe { VAL }; assert_eq!(READ_FROM_VAL, 0); } fn do_instead() { // Use an _immutable_ static containing an Atomic. // Note that Atomic variables are safe to share between threads (they // implement `Sync`). static COUNTER: AtomicU16 = AtomicU16::new(0); println!("Initial COUNTER value: {}", COUNTER.load(Ordering::Relaxed)); // Be sure to analyze your use case to determine the correct Ordering to // use. let old_count = COUNTER.fetch_add(1, Ordering::Relaxed); println!( "COUNTER after incrementing on the main thread: {}", old_count + 1 ); assert_eq!(COUNTER.load(Ordering::Relaxed), 1); // Spin up two new threads to further increment the static: let handle1 = thread::spawn(|| { for _ in 0..10_000 { COUNTER.fetch_add(1, Ordering::Relaxed); } }); let handle2 = thread::spawn(|| { for _ in 0..10_000 { COUNTER.fetch_add(1, Ordering::Relaxed); } }); handle1.join().unwrap(); handle2.join().unwrap(); let final_count = COUNTER.load(Ordering::Relaxed); println!("Final COUNTER value: {final_count}"); assert_eq!(final_count, 20_001); } fn or_do_that() { // The preferred way to handle mutable global state is to use // synchronization primitives with interior mutability // within an immutable `static` variable. // Use `Mutex` or `RwLock`, potentially wrapped in an `Arc`, // for safe concurrent access: static SAFE_COUNTER: Mutex<u32> = Mutex::new(0); println!( "Initial SAFE_COUNTER value: {}", SAFE_COUNTER.lock().unwrap() ); let safe_handle1 = thread::spawn(|| { for _ in 0..10_000 { let mut num = SAFE_COUNTER.lock().unwrap(); *num += 1; } }); let safe_handle2 = thread::spawn(|| { for _ in 0..10_000 { let mut num = SAFE_COUNTER.lock().unwrap(); *num += 1; } }); safe_handle1.join().unwrap(); safe_handle2.join().unwrap(); let final_count = *SAFE_COUNTER.lock().unwrap(); println!("Final SAFE_COUNTER value: {final_count}"); assert_eq!(final_count, 20_000); } fn main() { dont_do_this(); do_instead(); or_do_that(); }