How to Idiomatically Use Global Variables in Rust


How to Idiomatically Use Global Variables in Rust

Declaring and using global variables in Rust can be tricky. Typically for this language, Rust ensures robustness by forcing us to be very explicit.

In this article, I’ll discuss the pitfalls the Rust compiler wants to save us from. Then I’ll show you the best solutions available for different scenarios.

Overview

There are many options for implementing global state in Rust. If you’re in a hurry, here’s a quick overview of my recommendations.

A Flowchart for finding the best solution for global variables

You can jump to specific sections of this article via the following links:

A Naive First Attempt

Let’s start with an example of how not to use global variables. Assume I want to store the starting time of the program in a global string. Later, I want to access the value from multiple threads.

A Rust beginner might be tempted to declare a global variable exactly like any other variable in Rust, using let. The full program could then look like this:

use chrono::Utc;

let START_TIME = Utc::now().to_string();

pub fn main() {
    let thread_1 = std::thread::spawn(||{
        println!("Started {}, called thread 1 {}", START_TIME.as_ref().unwrap(), Utc::now());
    });
    let thread_2 = std::thread::spawn(||{
        println!("Started {}, called thread 2 {}", START_TIME.as_ref().unwrap(), Utc::now());
    });

    // Join threads and panic on error to show what went wrong
    thread_1.join().unwrap();
    thread_2.join().unwrap();
}

Try it for yourself on the playground!

This is invalid syntax for Rust. The let keyword can’t be used in the global scope. We can only use static or const. The latter declares a true constant, not a variable. Only static gives us a global variable.

The reasoning behind this is that let allocates a variable on the stack, at runtime. Note that this remains true when allocating on the heap, as in let t = Box::new();. In the generated machine code, there is still a pointer into the heap which gets stored on the stack.

Global variables are stored in the data segment of the program. They have a fixed address that doesn’t change during execution. Therefore, the code segment can include constant addresses and requires no space on the stack at all.

Okay, so we can understand why we need a different syntax. Rust, as a modern systems programming language, wants to be very explicit about memory management.

Let’s try again with static:

use chrono::Utc;

static START_TIME: String = Utc::now().to_string();

pub fn main() {
    // ...
}

The compiler isn’t happy, yet:

error[E0015]: calls in statics are limited to constant functions, tuple structs and tuple variants
 --> src/main.rs:3:24
  |
3 | static start: String = Utc::now().to_string();
  |                        ^^^^^^^^^^^^^^^^^^^^^^

Hm, so the initialization value of a static variable can’t be computed at runtime. Then maybe just let it be uninitialized?

use chrono::Utc;

static START_TIME;

pub fn main() {
    // ...
}

This yields a new error:

Compiling playground v0.0.1 (/playground)
error: free static item without body
 --> src/main.rs:21:1
  |
3 | static START_TIME;
  | ^^^^^^^^^^^^^^^^^-
  |                  |
  |                  help: provide a definition for the static: `= <expr>;`

So that doesn’t work either! All static values must be fully initialized and valid before any user code runs.

If you’re coming over to Rust from another language, such as JavaScript or Python, this might seem unnecessarily restrictive. But any C++ guru can tell you stories about the static initialization order fiasco, which can lead to an undefined initialization order if we’re not careful.

For example, imagine something like this:

static A: u32 = foo();
static B: u32 = foo();
static C: u32 = A + B;

fn foo() -> u32 {
    C + 1
}

fn main() {
    println!("A: {} B: {} C: {}", A, B, C);
}

In this code snippet, there’s no safe initialization order, due to circular dependencies.

If it were C++, which doesn’t care about safety, the result would be A: 1 B: 1 C: 2. It zero-initializes before any code runs and then the order is defined from top-to-bottom within each compilation unit.

At least it’s defined what the result is. However, the “fiasco” starts when the static variables are from different .cpp files, and therefore different compilation units. Then the order is undefined and usually depends on the order of the files in the compilation command line.

In Rust, zero-initializing is not a thing. After all, zero is an invalid value for many types, such as Box. Furthermore, in Rust, we don’t accept weird ordering issues. As long as we stay away from unsafe, the compiler should only allow us to write sane code. And that’s why the compiler prevents us from using straightforward runtime initialization.

But can I circumvent initialization by using None, the equivalent of a null-pointer? At least this is all in accordance with the Rust type system. Surely I can just move the initialization to the top of the main function, right?

static mut START_TIME: Option<String> = None;

pub fn main() {
    START_TIME = Some(Utc::now().to_string());
    // ...
}

Ah, well, the error we get is…

error[E0133]: use of mutable static is unsafe and requires unsafe function or block
  --> src/main.rs:24:5
  |
6 |     START_TIME = Some(Utc::now().to_string());
  |     ^^^^^^^^^^ use of mutable static
  |
  = note: mutable statics can be mutated by multiple threads: aliasing violations or data races will cause undefined behavior

At this point, I could wrap it in an unsafe{...} block and it would work. Sometimes, this is a valid strategy. Maybe to test if the remainder of the code works as expected. But it’s not the idiomatic solution I want to show you. So let’s explore solutions that are guaranteed to be safe by the compiler.

Continue reading
How to Idiomatically Use Global Variables in Rust
on SitePoint.

This article was republished from its original source.
Call Us: 1(800)730-2416

Pixeldust is a 20-year-old web development agency specializing in Drupal and WordPress and working with clients all over the country. With our best in class capabilities, we work with small businesses and fortune 500 companies alike. Give us a call at 1(800)730-2416 and let’s talk about your project.

FREE Drupal SEO Audit

Test your site below to see which issues need to be fixed. We will fix them and optimize your Drupal site 100% for Google and Bing. (Allow 30-60 seconds to gather data.)

Powered by

How to Idiomatically Use Global Variables in Rust

On-Site Drupal SEO Master Setup

We make sure your site is 100% optimized (and stays that way) for the best SEO results.

With Pixeldust On-site (or On-page) SEO we make changes to your site’s structure and performance to make it easier for search engines to see and understand your site’s content. Search engines use algorithms to rank sites by degrees of relevance. Our on-site optimization ensures your site is configured to provide information in a way that meets Google and Bing standards for optimal indexing.

This service includes:

  • Pathauto install and configuration for SEO-friendly URLs.
  • Meta Tags install and configuration with dynamic tokens for meta titles and descriptions for all content types.
  • Install and fix all issues on the SEO checklist module.
  • Install and configure XML sitemap module and submit sitemaps.
  • Install and configure Google Analytics Module.
  • Install and configure Yoast.
  • Install and configure the Advanced Aggregation module to improve performance by minifying and merging CSS and JS.
  • Install and configure Schema.org Metatag.
  • Configure robots.txt.
  • Google Search Console setup snd configuration.
  • Find & Fix H1 tags.
  • Find and fix duplicate/missing meta descriptions.
  • Find and fix duplicate title tags.
  • Improve title, meta tags, and site descriptions.
  • Optimize images for better search engine optimization. Automate where possible.
  • Find and fix the missing alt and title tag for all images. Automate where possible.
  • The project takes 1 week to complete.