Help make this code compile:

pub struct Bar {}

pub struct Foo {}

static bar_inst: Bar = Bar {};

impl Foo {
    static bar: Bar = Bar {};

}

I prefer to write Foo::bar instead of bar_inst. I am playing with macro_rules!. I believe the bar_inst approach forces me to thread the identifier name bar_inst through everything i.e. blah!(Foo, bar_inst) where as if I could get a static as Foo::bar, I could call the macro as blah!(Foo).

Question: In stable rust, is there anyway to fake/approximate a static that belongs to a struct/enum ?

Obligatory: you probably shouldn't use associated statics in the way you're describing...

Okay, with that out of the way: absolutely!
You've probably seen that while associated static variables aren't allowed, associated constants are.
For example, you can do this:

struct Foo {
    const bar: Bar = Bar {};
}

It's cool, it compiles, but, that doesn't help in your case where you want to be able to mutate the shared memory.

In order to do that, you can make use of "interior mutability".
There's a handful of 'special' wrapper types in Rust that allow you to mutate their content without needing mutable access to the wrapper itself. The de-factor option being RefCell.

You can emulate a static like you want by using an associated constant, with one of these wrapper types:

use std::cell::RefCell;

pub struct Bar {}

pub struct Foo {}

impl Foo {
    const BAR: RefCell<Bar> = RefCell::new(Bar {});
}

On it's own, it's not an exciting example, so here's an example playground you can run, showing how this would be usable.

1 Like
  1. Thank you for the working solution.

  2. I am curious to hear your intuition why.

This doesn't do what you want. The code you wrote in the playground doesn't notice because of the line let bar = Foo::BAR;, but if you actually use Foo::BAR twice, you will see that it is not shared:

use std::cell::RefCell;
pub struct Bar {
    flag: bool, 
}
pub struct Foo {}
impl Foo {
    const BAR: RefCell<Bar> = RefCell::new(Bar { flag: true });
}
fn main() {
    Foo::BAR.borrow_mut().flag = false;
    assert_eq!(false, Foo::BAR.borrow().flag); // this assertion fails
}

Constant items differ from static items:

Constants are essentially inlined wherever they are used, meaning that they are copied directly into the relevant context when used. This includes usage of constants from external crates, and non-Copy types. References to the same constant are not necessarily guaranteed to refer to the same memory address.

A static item is similar to a constant, except that it represents a precise memory location in the program. All references to the static refer to the same memory location.

You cannot use a const to create a shared memory location. (Notice also that if it were possible for the const value to be shared, you'd get an error because RefCell is not Sync and thus cannot be accessed from multiple threads.)

7 Likes

Of course, for values without interior mutability, constant static promotion can still result in a value of !Sync type referenced from multiple threads.

I should have said “You cannot use a const to create a shared mutable memory location.”

The pattern I usually use goes something like this:

impl Foo {
    pub fn bar()->&'static Bar {
        static bar: Bar = Bar;
        &bar
    }
}

Or, more likely:

impl Foo {
    pub fn bar()->&'static Bar {
        use std::cell::OnceLock;
        static bar_cell: OnceLock<Bar> = OnceLock::new();
        bar_cell.get_or_init(|| Bar::new(some_arg_that_is_not_const))
    }
}
2 Likes

LOL. Thanks. I was getting a weird error, but did not realize it was this.

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.