Reputation: 1792
Example code:
use std::sync::atomic::{AtomicU32, Ordering};
#[derive(Debug)]
struct Token(u32);
impl Token {
fn new() -> Self {
static COUNTER: AtomicU32 = AtomicU32::new(1);
let inner = COUNTER.fetch_add(1, Ordering::Relaxed);
Token(inner)
}
}
fn main() {
let t1 = Token::new();
let t2 = Token::new();
let t3 = Token::new();
println!("{:?}\n{:?}\n{:?}", t1, t2, t3);
}
When I run the code snippet shown above, it prints:
Token(1)
Token(2)
Token(3)
I found in Rust reference that static items' initialization are evaluated at compile time.
I wonder what exactly happens at runtime when the program executes up to the line that initializes the variable COUNTER
. What the compiled code looks like so that it could neglect the initialization?
Upvotes: 12
Views: 6659
Reputation: 65692
static
variables are handled the same whether they're defined at the module level or at the function level. The only difference is the scope for name resolution.
Many file formats for executables, including ELF and PE, structure programs in various sections. Usually, the code for functions is in one section, mutable global variables are in another section and constants (including string literals) are in yet another section. When the operating system loads a program into memory, these sections are mapped into memory with different memory protection options (e.g. constants are not writeable), as specified in the executable file.
When the documentation says that static
items are initialized at compile time, it means that the compiler determines the initial value of that item at compile time, then writes that value into the appropriate section in the compiled binary. When your program is run, the value will already be in memory before your program even gets the chance to run a single instruction.
The compiler is able to evaluate the expression AtomicU32::new(1)
because AtomicU32::new
is defined as a const fn
. Adding const
to a function definition means that it can be used in expressions that are evaluated at compile time, but const fn
s are much more limited than regular functions in what they can do. In the case of AtomicU32::new
though, all the function needs to do is initialize the AtomicU32
struct, which is just a wrapper around a u32
.
Upvotes: 16