DEV Community

Lozi
Lozi

Posted on

Global Variables in Rust & C/C++

What's a global variable?
A global variable is a variable with global scope, meaning that it visible throughout the program, unless it's shadowed. In compiled languages, global variables are generally static, which means their lifetime is the entire runtime of the program.

Which one's programming languages are actually compiled?
Is a programming language that is usually implemented with a compiler rather than an interpreter.

Why we use them?
Global variables are used to pass information between sections of code that do not share a relation like concurrent threads and signal handlers. Languages (Including C) defines implicitly a namespace for earch global variable inside a certain file so that eliminates most of the problems seen with languages with a global namespace, though problems may persist without their proper encapsulation. Without a proper locking (like mutex) code, using global variables will not be thread-safe except for read only values in protected memory.

Interesting, so using global variables in parallel programs is not the best idea UNLESS we use a proper locking (which is mutex)...
Rust is known as a safe programming language, therefore MAYBE this can be the cause of why I can't declare a global variable that can be edited at runtime: BECAUSE IT'S RISKY.
But my program isn't parallel, then why I can't do it then?

Let's investigate
I stumble upon a thread from stack overflow from a person who had somewhat the same problem that I'm facing currently, and the answer to that manner was to use the "unsafe" type in order to stop fighting with the compiler, but at a certain cost: "Unless your program wouldn't be in te future concurrent, then is perfectly fine to use the unsafe keyword".

But I want in the future make this decryption tool concurrent, so then I can learn more about concurrency in Rust. Thus, I think I might make the global variable "Safe".

But how do we make it "Safe"?
Let's investigate:
I stumble upon a really GOOD diagram explaining what to use depending on what do you wanna do with your global variable in Rust. You can find the diagram here: https://www.sitepoint.com/rust-global-variables/ At Overview section.

Rust Concurrent Tree Decision Taking

The thing that I need now is make my global variable "Safe", so then in the future I can use it without any problem.
For single threading if I wanna store at runtime inside the const, I have to use Rc. But since I've told already that I want to make this in the future a concurrent program. Then I'll use Arc.

What's Arc btw
I made a summary before about this Arc thing, so I might just copy and paste in case someone is interested:

Rc and Arc note:

https://app.notion.com/p/Rc-T-and-Arc-T-39bfcd22531880c1a6c0fb1446359574?source=copy_link

Solution to my problem

This was my code before the refactoring:

// std::array<std::vector<char>, 13> g_tables;
const G_TABLES: [Vec<u8>; 13] = [Vec::new(); 13];
Enter fullscreen mode Exit fullscreen mode

By combining Arc (Atomic Reference Counted) and Mutex (Mutual Exclusion), you create a single, shared source of truth that can be safely accessed and modified from anywhere in your program, even across multiple cores.

In the Rust community, wrapping a type like this is so common it's often jokingly called "the Arc> sandwich."

After:

static g_tables: Arc<Mutex<[Vec<u8>;13]>> = Arc::new(Mutex::new([Vec::new(); 13]));
Enter fullscreen mode Exit fullscreen mode

Ahhhh I'm so close but the Rust compiler is telling me something:

= note: the `Copy` trait is required because this value will be copied for each element of the array
help: create an inline `const` block
Enter fullscreen mode Exit fullscreen mode

But I don't understand why in order to get the Copy trait I have to put the Vec::new() inside a const...

What I found:
Copying vs. Re-running
When I write [value; 13], Rust doesn't run the value code 13 times
Instead, Rust does this:

  1. Evaluates Vec::new() exactly one
  2. Takes that single vector and tries to copy it 13 TIMES in order to fill the array

So this si where the Copy trait comes in. Simple types like u8 or booleans Implement Copy because copying them is just duplicating bits in memory.
But a Vec is: A dynamic data structure that points to a chunk of memory on the heap. So if Rust blindly copied the vector 13 times, you would end up with 13 vectors all pointing to the exact same memory location. So when you tried to add data to one, it would corrupt the others. And when the program ends, Rust would try to clean up the same memory 13 TIMES, causing a massive crash.

Solution: const Block
When we add the const block like the compiler tells us to do: [const { Vec::new() }; 13], we are changing the rules of the array syntax itself!!!.
I'm telling the compiler "Not evaluate this once, copy it instead, treat is as a constant expression, and run it 13 SEPARATE TIMES".

Arc (Atomic Reference Counting) is used to share ownership of a variable when you don't know how long it needs to live. But a static variable lives forever, for the entire duration of the program, and is already globally accessible to every thread.

If it's global, I only need the Mutex to safely mutate it!!!

Final Result:

// std::array<std::vector<char>, 13> g_tables;
static G_TABLES: Mutex<[Vec<u8>; 13]> = Mutex::new([const { Vec::new() }; 13]);
Enter fullscreen mode Exit fullscreen mode

Top comments (0)