How to make a local static variable like C has

In C you can have a local static variable inside a function that retains it's value between function calls.

It is very useful for caching, here is an example pseudo-code:

func call_many() {
  static int value;

  if (!value) {
    value = expensive_call();
  }

  return value;
}

the first time the function is called, the expensive calculation happens, we know that value will never change for the life of the program, so future calls just return the cached value.

this is so simple in C, but i can't work out how to do it in rust.

use std::sync::OnceLock;

fn call_many()->&'static MyStruct {
    static VALUE:OnceLock<MyStruct> = OnceLock::new();
    VALUE.get_or_init(|| MyStruct::expensive_new())
}
5 Likes

How to do it just in code?

4 lines of code in C shouldn't require pulling in 300 lines of code from somewhere.

1 Like

You can do almost the same as in C, but it has to be tagged unsafe because of the race condition UB risk (which also exists in the C version). You really shouldn’t, though, because Rust provides tools like OnceLock as part of the standard library to do this kind of thing risk-free.

/// Safety: `call_many` must never be called concurrently from multiple threads,
///         and `expensive_call` must never attempt to call `call_many`
unsafe fn call_many()->u32 {
    static mut VALUE:u32 = 0;

    if VALUE == 0 {
        VALUE = expensive_call();
    }

    return VALUE;
}
5 Likes

The C implementation is not thread safe, nor reentrant. The rust implementation using OnceLock is.

2 Likes

I mean, you can write C if you want.

The Rust solution uses only std, requires no unsafe and is not significantly more verbose than the C version (in fact, it's actually shorter, by a LoC metric).

4 Likes

The thing is, it doesn't. It's not the "4 lines of C" that requires it. The Rust version is correct (sound), while the C version is incorrect (unsound), as others have already pointed it out.

As for not pulling in any dependencies (even std, although that hardly hurts anyone), you can just do the right thing and not use globals at all. If you need to cache the value, put it in a local variable, and pass it as an argument to whatever needs it. It's not that hard. You can even imitate the lazy invocation using Option::get_or_insert_with.

5 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.