Proper variable scoping

This is probably been asked before but I can't find it. I am coming from the classic object-oriented world (C, C#, Python, etc.) and am trying to figure out how to implement the equivalent of a library with library scoped variables. I have a number of groups of functions that need to share variables within each group, i.e. a classic 'library'. I see that Rust modules might be the way to go but can't figure out how to limit the scope of variables to a specific module. Maybe this is not the right approach but I am not sure what it.
I understand global static variables so my question being are they the right approach to limiting the variable scope to a module? If so then how would I acheive that? In C or C# I would do this is a class but how do I do this in Rust?

Thanks in advance for your help.

Global variables are usually not idiomatic unless they are constants. Consider putting them in a struct?

2 Likes

Global variables (that are not constant, or effectively-constant like lazy init — variables that are global state) should be avoided whenever possible. The usual, straightforward reason for this is that they make it hard to see interactions (or 'dependencies' or 'coupling' if you like) between different parts of the code, and hard to write tests of the functions that use the global state. And, if an application has an actual use for the thing your library made global actually being in two different states, the application's code has to be messier.

From a Rust-specific angle, there are two more reasons to emphasize:

  • Rust makes it easy to have concurrency, either with threads or async. Thus, if you have any global state it must be thread-safe — but even then, does it actually make sense to have multiple threads accessing it? Better to let each task have its own state, or whatever else suits the application.
  • Cargo makes it easy to bring in dependencies — even multiple versions of the same library (though that's usually an accident or unfortunate necessity than a specific need). If your library has global state, then that state is shared among all dependents which depend on (the same major version of) your library. A good library should not do this because it should be independently usable by any crate that wants it.

So: please don't design a library with global state.

If you tell us more about the use case, we can suggest an alternative.

3 Likes

My question may have come across wrong...I want to AVOID global but a ot of docs I read say to use it. I want module/library local only state variables.

As I dig more I think creating crates MIGHT be the approach I want.

“module/library local only state variables” are still global variables. Something being global state means that code using the library can't choose to create two of it instead of just one.

Put your state in structs, not statics. Then it won't be “global state” in the sense we mean.

2 Likes

Modules and libraries are not typically instantiated while the program is running, so you wouldn't typically have the ability to create any kind of library-local state variable. More likely, it would be a global static that may not be visible outside of the library, but the point is, there are two different concepts here: when/where the item is instantiated (allocated/deallocated), and where it is visible.

You can use various pub qualifiers in combination with pub use & private modules to export symbols to be visible from wherever you like. You can make statics that are restricted in such a way, but as others have pointed out, you rarely want to do this. (Certainly if you can't explain why you need it.)

One pattern that I've seen used in many languages, not just Rust, is to put the state in some sort of Context struct and make sure any functions/structs that need access to those variables are given a &Context.

This lets you hoist module-specific state into an object that can be passed around to anything that needs it. It also lets you avoid introducing a hidden dependency and, more specific to Rust, you won't have ownership/borrowing issues (mutable static variables can be referenced from anywhere and are unsynchronised shared mutable variables - a big no-no in Rust).

There was a similar discussion about this "Context" pattern a while back which you may find informative:

I truly appreciate the replies I have received on this. After reading everyone's comments I have worked out the solution. Using a struct and passing &mut reference to a module instance of the struct. Thanks for all the grat advice.

1 Like

I did just that and it is running great now, thanks.

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.