1

I am writing a multithread program in rust, where I need a shared object to keep track of the state of the application. Coming from a C++ background I would have solved this with a unique pointer wrapping a static object (a singleton implementation basically). In rust I am a little bit confused since static objects are very different. Looking around on the web I found the lazy_static crate, so I handled my problem in this way:

the structure that handles my state:

struct Status {
    pub enabled: bool,
    pub num_file_moved: u64,
    pub num_file_moved_err: u64,
    pub tot_size: u64,
}

the static object that contains the status of the application:

use lazy_static::lazy_static;

lazy_static! {
    static ref STATUS: Mutex<Status> = Mutex::new(Status::default());
}

And then I wrote a series of method that allows to read/write the status object, e.g.:

pub fn enabled() -> bool {
    STATUS.lock().unwrap().enabled
}

pub fn set_enable(new_val: bool) {
    STATUS.lock().unwrap().enabled = new_val;
}

Is this the correct way to handle this issue? My doubt is that I am trying to bring a C++ concept to Rust.

nicFlower
  • 53
  • 7
  • 1
    It's definitely *one* way to solve this problem. I don't think it's a horrible solution, either. Although, singletons/global static state is discouraged in general. But that's not a Rust thing, that also applies to C++. – Finomnis Sep 14 '22 at 06:50
  • 2
    The way to solve this without a singleton would be `Arc>` passed around as a parameter. – Finomnis Sep 14 '22 at 06:54
  • 3
    You can convert fields to the [atomic](https://doc.rust-lang.org/std/sync/atomic/#structs) types instead of locking the whole struct(for this example) – Ömer Erden Sep 14 '22 at 06:56
  • 3
    In general, though, you have to be very careful with race conditions. Especially the read-modify-write situation happens very quickly, especially if you write wrappers like `enabled()` / `set_enable()`. – Finomnis Sep 14 '22 at 06:57
  • Also new to rust; out of interest, would you consider using a channel to communicate updates? – w08r Sep 14 '22 at 09:06
  • 1
    @ÖmerErden that works if there are only independent data items, but if there are dependencies simply using `atomic` can lead to inconsistent state. Here it seems likely the fields are strongly inter-related, so atomics would be a bad idea, possibly aside from using immutable objects and CAS-ing an `AtomicPtr`. – Masklinn Sep 14 '22 at 09:20
  • @Masklinn _instead of locking the whole struct_ was the key point there, If it is necessary of course locking the whole struct can be a better solution, my comment was not meant to oppose the other ideas. For the _"Here it seems likely the fields are strongly inter-related"_ I believe it is highly arguable – Ömer Erden Sep 14 '22 at 10:11
  • "If it is necessary of course locking the whole struct can be a better solution" the problem is that you're giving a blanket suggestion without explaining its caveats. "I believe it is highly arguable" two of the fields have essentially the same name, and the third would almost certainly be related. That the `enabled` flag would also be linked is hardly unimaginable. – Masklinn Sep 14 '22 at 10:58
  • 1
    @Masklinn _"two of the fields have essentially the same name"_: One represents success case other is a failure, third one(`tot_size`) is most likely initial and immutable, only enabled looks like it can be accessed continuously and can be written at some random time. Probably you can find some **niche** context which these variables may depend to each other but most likely they are not. – Ömer Erden Sep 14 '22 at 11:15
  • You can take inspiration from the [crate log](https://docs.rs/log/latest/src/log/lib.rs.html#204) implementation which use something like a singleton – Zeppi Sep 14 '22 at 11:33
  • ÖmerErden Masklinn: I think you both are arguing based on vague assumptions. Let's agree that this dispute is only solvable with more context :) – Finomnis Sep 14 '22 at 13:25
  • 1
    @w08r In general, sure, but that would convert your entire state updates from function call based to event based. Not saying it's a bad idea, it's a pattern that is well known and understood, it's just different. The problem with channels, though, is that you can't really wait for the change to apply. Channels are more of a fire-and-forget thing. – Finomnis Sep 14 '22 at 13:28
  • 1
    I would vote this as a duplicate of [How do I create a global, mutable singleton?](/q/27791532/2189130) (but I don't want to dupe-hammer it just yet). The primary answer there is essentially "don't use globals, but if you must, then here's how to do it" with examples that match how OP has done it, effectively agreeing: yes, this a correct way to handle it. I think this reflects the comments listed here. – kmdreko Sep 14 '22 at 16:14

0 Answers0