How to access Global Varaible

I'm looking for best ways one can import a variable from one module to other module. Like make it a global variable and have it mutated in other module. Below is the idea:

  • src/global.rs
static mut COLLECTION: Vec<i32> = Vec::new();
  • src/main.rs
mod global;
use global::COLLECTION;
fn main() {
    // Do something and push result in collection like in a loop
    loop {
        let x = 3;
        COLLECTION.push(x);
        if COLLECTION.len() == 10 {
            break;
        }
    }
}

fn some_other_func() -> usize {
    COLLECTION.len()
}

I'm also open to other approaches like I have seen rc module but unable to understand how it would help in this kind of scenario.

But having a collection that can be accessed in any function is the main objective.

static mut is almost never the correct option in Rust.
For a single-threaded program, you can use the thread_local! macro from the std library (with a RefCell for mutation).
For multithreaded situation, the lazy_static crate with a Mutex accomplishs the same thing.

3 Likes

Why can't you pass it as an argument ?

let mut collection = Vec::new();
module1::func(&mut collection);
module2::func(&mut collection);

There's generally nothing stopping you from just having function arguments.

6 Likes

My question is: Are you really, really sure you need whatever it is to be a global variable?

Generally this is not so and not a very good way of designing a program.

1 Like

Sorry for not making it clear. I have re-written the code and let me know how to achieve this:

mod my_module {

    pub struct Foo {
        id: usize,
    }
    impl Foo {
        pub fn new(id: usize) -> Self {
            // I shouldn't pass the Collection struct, as the user doesn't know about Collection.
            let foo_obj = Self { id };
            // I need to some how append foo_obj to Collection struct collection.foos.push(foo_obj).
            foo_obj
        }
    }

    // Returns number of total foos from the Collection
    pub fn total_foos() ->usize{
        // return the total foos
    }

    // Collects Foos
    struct Collection {
        foos: Vec<Foo>,
    }
    impl Collection {
        fn len(&self) -> usize {
            self.foos.len()
        }
    }
    impl Default for Collection {
        fn default() -> Self {
            Self { foos: Vec::new() }
        }
    }
}
fn main() {
    use my_module::{Foo,total_foos};
    let foo_value = Foo::new(5); // As a user of my_module, I need not care about Collection
    let foo1_value=Foo::new(3);
    println!("Total foos are {}",total_foos()) // Should tell there are two foos
}

Do you actually need to store the Foos in the collection, or are you just tracking how many there are?

I need to collect them.

This definitely smells like an antipattern, because now the problem is not just that you need a global variable, but you also need shared ownership in order to simultaneously keep an object in the collection as well as return it to the consumer of your library. For that purpose you will need Rc or Arc.

Potentially this sounds like something that could be solved with a collection like a Slot Map where you could return a "handle" to the consumers of your library, rather than an instance of the type directly.

It may also help if you describe what you are trying to accomplish at a higher level as perhaps we can advise on a pattern that is more in line with Rust idioms.

2 Likes

Thx for responding. dual ownership is a problem as u said.

Let me give you the context. I'm trying to build a stock data fetching binary that gets data from a remote url based on the collection of stock codes.

The user adds stock code and in the background I was thinking we can collect them. After which with a single call the data for the collection of stock codes will get fetched.

This sounds potentially like a good opportunity for the ""builder pattern". There's a good example in the standard library in the form of OpenOptions.

Without global variables or shared memory, you could have something like this:

StockInterests::new()
    .add(1234)
    .add(5678)
    .fetch();

Please check this code. It will give you a better understanding on my requirement. It will tell you Why I'm not interested in passing collection as an argument.

  1. I know about this pattern I.e initialize a collection and then add objects.

But I was thinking :thought_balloon: how can I track all those initialized objects in the background.

  1. How does a builder pattern be helpful here?

Make a struct wrapping a collection of Foo, make all your methods related to Foos be methods on that struct. Now you've essentially "curried" the collection argument from all the functions and you don't have to care about it. Example. The outer modules won't care about the collection itself just the exposed API, the inner modules will have access to it.

If you absolutely want a global collection in your module :

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

static COLLECTION: Lazy<Mutex<Vec<Foo>>> = Lazy::new(Default::default);

And just clone the Foos you make into it.

Thanks

This is one way to approach the problem.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=1b5583e90e6e6bdc3d8b968b6512d8d5

Credits: @saiumesh535

Note that instead of lazy_static you can use parking_lot with const_mutex():

use parking_lot::{const_mutex, Mutex};

static COLLECTION: Mutex<Vec<Foo>> = const_mutex(Vec::new());

Playground

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.