4

I'm currently using the thread_local! macro:

thread_local!(static FOO: RefCell<Foo> = RefCell::new(Foo::new()));

I only ever need immutable references to FOO - it's read only. Its purpose is to cache the result of an expensive calculation, so that the result can be used in many places in the rest of the program, without being explicitly passed through many levels of function and method invocations.

Rust allows multiple readers, if there is no writer.

Is there a way that I can create a read-only global variable FOO at the start of main() (or before) and get read-only access to it from multiple threads (which are all spawned after FOO is initialised)?

I've looked at lazy_static but that has lazy initialisation, which implies that there are runtime checks to see if it has been initialised yet. I'm looking for something which compiles to just a memory address (in the code which uses it) where the content of that memory is initialised at (or before) the start of main() and is never changed.

0

1 Answer 1

6

This really feels like it's unnecessary except in the absolute rarest of performance-critical situations.

There are crates (e.g. ctor) that let you do computations before main, but most of std cannot be used before main, so you'll be extremely limited in what you can do. If we're going to do this, then lets do it as the first thing in main:

// result of expensive calculation
#[derive(Debug)]
pub struct Foo(String);

static mut FOO: *const Foo = std::ptr::null();

fn init_foo() {
    // expensive calculation
    let foo = Box::new(Foo(String::from("expensive!")));
    unsafe {
        // leak the value, so it will never be dropped or freed
        FOO = Box::leak(foo) as *const Foo;
    }
}

// public accessor for Foo
pub fn get_foo() -> &'static Foo {
    unsafe {
        &*FOO
    }
}

fn main() {
    // better remember to do this or it's UB!
    init_foo();
    
    println!("foo = {:?}", get_foo());
}
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks, this shows me what I need to do. I'll also try making it safe from UB with techniques from stackoverflow.com/questions/42036826/… - this looks like its possible to enforce a compile-time check that init_foo() was called before get_foo().
I think the situation is not actually that rare. The performance impact is minimal if you are only considering the added cost of a branch, but adding a branch can make a function large enough of the compiler decides to no longer inline it, and if the compiler doesn't inline it it can miss other optimization opportunities, so the impact of small things like this can actually be surprisingly large in some situations. Also the branch table and instruction cache in the CPU has are finite, and if your program is littered with accesses all of which come with their own branch, the cost can add up.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.