0x00A5
0x00A5

Reputation: 1792

Initialization of a static variable in the scope of a function or a module

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

Answers (1)

Francis Gagné
Francis Gagné

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 fns 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

Related Questions