How do I create variable shared by all instances of a struct?

I wanted to get the same as static variables in C++ -- a variable shared between all instances of a struct and can be modified. However, the below code does not work...how is this done in rust?

struct Foo{
    static a: usize, 
}
// in main.rs I wanted to write something like Foo::a = 5. 

EDIT: here's the specific use case I have in mind. I have a struct called Polynomial and all instances should have a common degree which is an int. There are also a vector of constants v which facilitates multiplying two polynomials together, and is parametrized by the degree. So overall I wish to have the two variables n: int and v: Vec<i32> visible to all the instances of the struct, but the precise value of n will be given by user and v will be computed from n at the beginning of program. So the files in my hope could looks like

struct Polynomial{
 // common n: int, 
 // common v: Vec<i32>, 

  pub fn multiply(&self, Other:self) -> Self{
      // make use of n and v. 
  }
}

and then specify the values at runtimes like

fn main(){
    Polynomial::n = get_n_from_user(); 
    Polynomial::v = compute_v_from_n(n); 
}

so I wanted to see how to make the above work in Rust. Thanks!

EDIT2: So I realized that I don't actually need to "mutate" the variable. I just need it to be able to set at runtime. I was trying with const keyword but that requires me to hard-code the value so that does not work.

1 Like

Put the static variable in the same module as the struct. It is not part of the struct's definition.

Edit: I just noticed your request for mutability.
In this case you need to use unsafe (static mutability is unsafe).

Make it global outside the struct. use OnceCell (for lazy initialization) with a Mutex (for interior mutability) to lazy initialize it and make it mutable.

if you're using async stuff use an async Mutex. tokio, async_std all have their own.

Edit: RefCell can't be used here, so updated with a Mutex instead.

4 Likes

Thanks! Could you point me to a small example for how to use the OnceCell and RefCell?

No, please don’t use mutable static variables:

There is basically no situation where static mut is useful [...] (read whole post)


I thought you can only put Sync values into statics, so RefCell shouldn’t work, right?


I would suggest using a Mutex; or perhaps an AtomicUSize when it’s just an integer value.

3 Likes

Can you give more detail about the use case for this? There may be a better solution than just putting a static in the module.

Yeah apparently RefCell Can't be used here

Here's one with OnceCell and Mutex. OnceCell is an external crate fyi

use once_cell::sync::Lazy;
use std::sync::Mutex;

static GLOBAL: Lazy<Mutex<i32>> = Lazy::new(|| Mutex::new(1));

fn main() {
    println!("{}", *GLOBAL.lock().unwrap());
    *GLOBAL.lock().unwrap() = 2;
    println!("{}", *GLOBAL.lock().unwrap());
}

As you can see it has nothing to do with any struct, you're gonna have to assume the association. or you could make the variable private and expose it through the struct's associated functions if that makes you feel better.

2 Likes

How about counting instances of a certain struct. That's the first use case of static variables in a class they teach in school

Just to avoid any potential XY Problem, may I ask you what you are trying to achieve by using a mutable global variable? Or is this just an abstract question of "how to do something similar to this C++ feature"?

Just noticing, @Kestrer pretty much asked the same question already.

1 Like

I was asking about @haochenuw's specific use case here, I understand that in general statics can be useful. :slight_smile:

1 Like

Good point! Just edited the post to explain my use case

1 Like

I agree it isn't necessary most of the time. It was not my intention to suggest that unsafe is the best way to accomplish any specific task, but the OP's question (originally) was basically "What is the the Rust equivalent of C++ statics?"
That would be an unsafe mutable static variable, so I stand by my answer from a 'learning the language' standpoint, if not from a 'best practices' standpoint. I even included a link to the relevant section of the Book, which includes this quote: "Mutable statics are still very useful, however. They can be used with C libraries and can also be bound from C libraries in an extern block."

1 Like

Great!

If your program only ever needs to set the values once throughout the entire program run, you could use a OnceCell here. It does offer more functionality than what is used here (in particularly some synchronization and blocking when multiple threads try to initialize it concurrencly) but after that, read accesses should be very fast.

use std::ops::Mul;
use once_cell::sync::OnceCell;

struct Polynomial {
    whatever_internal_fields: i32,
}

impl Polynomial {
    // if you *really* want to have the statics
    // under `Polynomial::`, you could do it like this.
    // You might as well use a module `polynomial` to
    // indicate the connection.
    fn n() -> &'static OnceCell<i32> {
        static N: OnceCell<i32> = OnceCell::new();
        &N
    }
    fn get_n() -> i32 {
        *Self::n().get().unwrap()
    }
    fn v() -> &'static OnceCell<Vec<i32>> {
        static V: OnceCell<Vec<i32>> = OnceCell::new();
        &V
    }
    fn get_v() -> &'static [i32] {
        Self::v().get().unwrap()
    }
    
    fn multiply(&self, other: &Self) -> Self {
        println!("I can see: {:?}", (Self::get_n(), Self::get_v()));
        Self {
            whatever_internal_fields: 0,
        }
    }
    
    fn new() -> Self {
        Self {
            whatever_internal_fields: 0,
        }
    }
}
impl Mul<&Polynomial> for &Polynomial {
    type Output = Polynomial;
    fn mul(self, other: &Polynomial) -> Polynomial {
        self.multiply(other)
    }
}



fn main() {
    Polynomial::n().set(42).unwrap();
    Polynomial::v().set((1..10).collect()).unwrap();
    
    let p = Polynomial::new();
    &p * &p;
}

(playground)

There is of course also ways to do this without any global variables

use std::ops::Mul;
#[derive(PartialEq, Eq, Debug)]
struct PolynomialContext {
    n: i32,
    v: Vec<i32>,
}

struct Polynomial<'a> {
    ctx: &'a PolynomialContext,
    whatever_internal_fields: i32,
}

impl<'a> Polynomial<'a> {

    fn multiply(&self, other: &Self) -> Self {
        debug_assert_eq!(self.ctx, other.ctx);
        println!("I can see: {:?}", (self.ctx.n, &self.ctx.v));
        Self {
            ctx: self.ctx,
            whatever_internal_fields: 0,
        }
    }
    
    fn new(ctx: &'a PolynomialContext) -> Self {
        Self {
            ctx,
            whatever_internal_fields: 0,
        }
    }
}
impl<'a> Mul<&Polynomial<'a>> for &Polynomial<'a> {
    type Output = Polynomial<'a>;
    fn mul(self, other: &Polynomial<'a>) -> Polynomial<'a> {
        self.multiply(other)
    }
}



fn main() {
    let ctx = PolynomialContext {
        n: 42,
        v: (1..10).collect(),
    };
    let p = Polynomial::new(&ctx);
    &p * &p;
}

(playground)

Finally, if you do need the ability to change the values multiple times, you could use an RwLock instead of a Mutex to prevent concurrent reads from blocking each other. In a multi-threaded setting with rarely-mutated RwLocks, there’s also the option of crossbeam::sync::SharedLock for better performance. If you go the Mutex or RwLock route, you might want to also use an Option inside it to have the values for n and v uninitialized at the start of the program (instead of needing to make up non-sensical default-values for them that might lead to bugs).

2 Likes

Rust doesn't have a built-in distinction between a global that is set only once and one that can be set multiple times, so to make it safe the mutability needs to be managed somewhere. My go-to crate for that is lazy_static, but there are lots of other options.

As @m51 says, you very much can implement this directly with mutable statics, which works pretty much the same way as in C++. Like in C++, multiple threads could read and write to the same static variable at once, so you need to verify manually that all possible ways that the code could run are safe. In this case, you write once then read multiple times, which is OK.

It would look like this:

// initial setup
static mut POLYNOMIAL_N: i32 = 0;
static mut POLYNOMIAL_V: Vec<i32> = Vec::new();

// writing
unsafe {
    POLYNOMIAL_N = get_n_from_user();
    POLYNOMIAL_V = compute_v_from_n(POLYNOMIAL_N);
}

// reading
unsafe {
    // make use of n and v.
    println!("{:?}", POLYNOMIAL_N);
    println!("{:?}", POLYNOMIAL_V);
}

A full example is here:

The big problem with this, along with all the other options that share a single static variable for v and n, is that it makes the Polynomial type much harder to use. For example, by default Rust runs unit tests in parallel on multiple threads, so if you wanted to have different tests which use different values for v and n, then they will break each other if they share a common static variable. You'd need to make sure that only one unit test runs at once, which will make them run more slowly and be prone to error.

One way to make the Polynomial type easier to use would be to store a pointer to the params from the Polynomial type, rather than making the parameters static. That would look like this:

struct Params {
    n: i32,
    v: Vec<i32>,
}

struct Polynomial {
    params: Arc<Params>,
    // other fields
}

Then you can access n and v normally from multiply as self.params.n etc, and then there's no need to worry about the safety issues. Full example here:

1 Like

what does this mean actually? other than access control, is this in any way different from a global one?

No, it is not different in any way. Rust allows you to add any item in a function body. This includes, other functions, types, statics, consts. Curiously enough it even allows mod:

fn a() {
    #[path = "lib.rs"]
    mod b;
}
error: circular modules: src/lib.rs -> src/lib.rs
 --> src/lib.rs:3:5
  |
3 |     mod b;
  |     ^^^^^^

error: aborting due to previous error
1 Like

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.