235

I know that in general, global-variables are to be avoided. Nevertheless, I think in a practical sense, it is sometimes desirable (in situations where the variable is integral to the program) to use them.

In order to learn Rust, I'm currently writing a database test program using sqlite3 and the Rust/sqlite3 package on GitHub. Consequently, that necessitates (in my test-program) (as an alternative to a global variable), to pass the database variable between functions of which there are about a dozen. An example is below.

  1. Is it possible and feasible and desirable to use global variables in Rust?

  2. Given the example below, can I declare and use a global variable?

extern crate sqlite;

fn main() {
    let db: sqlite::Connection = open_database();

    if !insert_data(&db, insert_max) {
        return;
    }
}

I tried the following, but it doesn't appear to be quite right and resulted in the errors below (I tried also with an unsafe block):

extern crate sqlite;

static mut DB: Option<sqlite::Connection> = None;

fn main() {
    DB = sqlite::open("test.db").expect("'test.db' should be readable");
    println!("Database Opened OK");

    create_table();
    println!("Completed");
}

// Create Table
fn create_table() {
    let sql = "CREATE TABLE IF NOT EXISTS TEMP2 (ikey INTEGER PRIMARY KEY NOT NULL)";
    match DB.exec(sql) {
        Ok(_) => println!("Table created"),
        Err(err) => println!("Exec of Sql failed : {}\nSql={}", err, sql),
    }
}

Errors that resulted from compile:

error[E0308]: mismatched types
 --> src/main.rs:6:10
  |
6 |     DB = sqlite::open("test.db").expect("'test.db' should be readable");
  |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected enum `std::option::Option`, found struct `sqlite::Connection`
  |
  = note: expected type `std::option::Option<sqlite::Connection>`
             found type `sqlite::Connection`

error: no method named `exec` found for type `std::option::Option<sqlite::Connection>` in the current scope
  --> src/main.rs:16:14
   |
16 |     match DB.exec(sql) {
   |              ^^^^
4
  • 8
    For a safe solution, please see How do I create a global, mutable singleton?. Commented Mar 5, 2017 at 15:08
  • 2
    I should note here that the errors that OP is experiencing have to do with trying to store a Connection inside an Option<Connection> type, and trying to use an Option<Connection> as a Connection. If those errors were resolved (by using Some()) and they used an unsafe block, as they originally tried, their code would work (albeit in a thread-unsafe way). Commented Mar 12, 2020 at 22:43
  • Does this answer your question? How do I create a global, mutable singleton? Commented May 9, 2020 at 16:57
  • DB is an option of a connection and you assigned a connection to it. But you shouldn't use global variables, instead pass a reference to the connection instead. Commented Mar 8, 2024 at 8:48

10 Answers 10

125

It's possible, but heap allocation is not allowed directly. Heap allocation is performed at runtime. Here are a few examples:

static SOME_INT: i32 = 5;
static SOME_STR: &'static str = "A static string";
static SOME_STRUCT: MyStruct = MyStruct {
    number: 10,
    string: "Some string",
};
static mut db: Option<sqlite::Connection> = None;

fn main() {
    println!("{}", SOME_INT);
    println!("{}", SOME_STR);
    println!("{}", SOME_STRUCT.number);
    println!("{}", SOME_STRUCT.string);

    unsafe {
        db = Some(open_database());
    }
}

struct MyStruct {
    number: i32,
    string: &'static str,
}
Sign up to request clarification or add additional context in comments.

5 Comments

with the static mut option, does it mean that every piece of code that uses the connection has to be marked as unsafe ?
@Kamek The initial access has to be unsafe. I typically use a thin wrapper of a macro to mask that.
@jhpratt I think putting unsafe in a macro kind of defeats the borrow-checker's purpose. You're going to segfault if you touch db mutably from two different location, probably best to keep the unsafe there so you explicitly say "I may segfault if I don't borrow-check myself". Unless your macro has "unsafe" in its name, in which case carry on.
You're absolutely right @NicholasPipitone. I'm not even entirely sure what I meant by that comment from over two years ago.
static mut is just not the right call here. The correct thing to have is a OnceLock or LazyLock.
91

You can use static variables fairly easily as long as they are thread-local.

The downside is that the object will not be visible to other threads your program might spawn. The upside is that unlike truly global state, it is entirely safe and is not a pain to use - true global state is a massive pain in any language. Here's an example:

extern mod sqlite;

use std::cell::RefCell;

thread_local!(static ODB: RefCell<sqlite::database::Database> = RefCell::new(sqlite::open("test.db"));

fn main() {
    ODB.with(|odb_cell| {
        let odb = odb_cell.borrow_mut();
        // code that uses odb goes here
    });
}

Here we create a thread-local static variable and then use it in a function. Note that it is static and immutable; this means that the address at which it resides is immutable, but thanks to RefCell the value itself will be mutable.

Unlike regular static, in thread-local!(static ...) you can create pretty much arbitrary objects, including those that require heap allocations for initialization such as Vec, HashMap and others.

If you cannot initialize the value right away, e.g. it depends on user input, you may also have to throw Option in there, in which case accessing it gets a bit unwieldy:

extern mod sqlite;

use std::cell::RefCell;

thread_local!(static ODB: RefCell<Option<sqlite::database::Database>> = RefCell::New(None));

fn main() {
    ODB.with(|odb_cell| {
        // assumes the value has already been initialized, panics otherwise
        let odb = odb_cell.borrow_mut().as_mut().unwrap();
        // code that uses odb goes here
    });
}

1 Comment

You have to compile sqlite with threadsafe so it will use system (kernel) level mutexes. See the warning here sqlite.org/faq.html#q6 The sqlite package eventually compiles sqlite itself here github.com/stainless-steel/sqlite3-src/blob/master/build.rs How can you really be sure it's "entirely safe and is not a pain to use"? Might be better to have 1 thread handling the sqlite connection and other threads sending messages to that thread, instead of asking the sqlite API to open the same database file multiple times.
39

Look at the const and static section of the Rust book.

You can use something like the following:

const N: i32 = 5;

or

static N: i32 = 5;

in the global space.

But these are not mutable. For mutability, you could use something like:

static mut N: i32 = 5;

Then reference them like:

unsafe {
    N += 1;

    println!("N: {}", N);
}

4 Comments

Please do explain the difference between const Var: Ty and static Var: Ty?
@Nawaz const makes the global variable immutable, while static makes it mutable. Note that assignments to static variables are unsafe.
@sb27 Actually const makes it just a value that the compiler can inline wherever it wants to, and which won't actually occupy a fixed place in memory. (It's presumably not placed anywhere by the linker.) And static does not make it mutable. What makes it mutable is mut. At least, if I understand correctly... I've been learning Rust for only a few days.
@PeterHansen Hello fellow Rustacean, I hope you enjoy the language ^^, But you are absolutely right, atleast the documentation says so (See doc.rust-lang.org/std/keyword.const.html).
22

I am new to Rust, but this solution seems to work:

#[macro_use]
extern crate lazy_static;

use std::sync::{Arc, Mutex};

lazy_static! {
    static ref GLOBAL: Arc<Mutex<GlobalType> =
        Arc::new(Mutex::new(GlobalType::new()));
}

Another solution is to declare a crossbeam channel transmit/receive pair as an immutable global variable. The channel should be bounded and can only hold one element. When you initialize the global variable, push the global instance into the channel. When using the global variable, pop the channel to acquire it and push it back when done using it.

Both solutions should provide a safe approach to using global variables.

2 Comments

There's no point to &'static Arc<Mutex<...>> because it can never be destroyed and there's no reason to ever clone it; you can just use &'static Mutex<...>.
This works quite well in my tests. Just to add a little on it, it can be used like this: let mut user = GLOBAL.lock().unwrap(); and from there it's used like a regular GlobalType. And it is also thread safe with the mutex usage.
19

Heap allocations are possible for static variables if you use the lazy_static macro as seen in the documentation:

Using this macro, it is possible to have statics that require code to be executed at runtime in order to be initialized. This includes anything requiring heap allocations, like vectors or hash maps, as well as anything that requires function calls to be computed.

// Declares a lazily evaluated constant HashMap. The HashMap will be evaluated once and
// stored behind a global static reference.

use lazy_static::lazy_static;
use std::collections::HashMap;

lazy_static! {
    static ref PRIVILEGES: HashMap<&'static str, Vec<&'static str>> = {
        let mut map = HashMap::new();
        map.insert("James", vec!["user", "admin"]);
        map.insert("Jim", vec!["user"]);
        map
    };
}

fn show_access(name: &str) {
    let access = PRIVILEGES.get(name);
    println!("{}: {:?}", name, access);
}

fn main() {
    let access = PRIVILEGES.get("James");
    println!("James: {:?}", access);

    show_access("Jim");
}

1 Comment

An existing answer already talks about lazy static. Please edit your answer to clearly demonstrate what value this answer brings compared to the existing answers.
15

enter image description here

I think this page covers most aspects well https://www.sitepoint.com/rust-global-variables/

1 Comment

This graph looks helpful but Interior Mutability is not a friend for static T because none of cell-types are sync, so there should be another thread telling about Mutex, RwLock.
14

As of Rust 1.70, there is also the OnceLock synchronization primitive we could use in cases where we just need a static global variable that is initialized (written to) only once.

Here is an example of a static global read-only HashMap:

use std::collections::HashMap;
use std::sync::OnceLock;

static GLOBAL_MAP: OnceLock<HashMap<String, i32>> = OnceLock::new();

fn main() {
    let m = get_hash_map_ref();
    assert_eq!(m.get("five"), Some(&5));
    assert_eq!(m.get("seven"), None);
    std::thread::spawn(|| {    
        let m = get_hash_map_ref();
        println!("From another thread: {:?}", m.get("five"));
    }).join().unwrap();
}

fn get_hash_map_ref() -> &'static HashMap<String, i32> {
    GLOBAL_MAP.get_or_init(|| {
        create_fixed_hash_map()
    })
}

fn create_fixed_hash_map() -> HashMap<String, i32> {
    let mut m = HashMap::new();
    m.insert("five".to_owned(), 5);
    m.insert("ten".to_owned(), 10);
    m
}

As can be seen, that map can be accessed from different threads.

Note that HashMap::new() is not const (at least not yet) and that's why we (still) cannot have something like const MY_MAP: HashMap<...> = ... in Rust.

1 Comment

Thanks for this. Coding in Rust for more than 3 years now, my solution needed a lot of work and outdated. This new feature is a different solution but greatly quality of life improvement! :) Good thing I checked online just to make sure.
2

Use crate once_cell or lazy_static, or SyncOnceCell in nightly.

1 Comment

0

The simplest method is to use a mutex. A mutex, or "mutual exclusion", is a synchronization primitive provided by your OS that prevents different threads from simultaneously accessing some data. When one thread calls lock() on the mutex when it's already been locked, that thread will "freeze" until the first lock has been dropped.

use std::sync::Mutex;

static GLOBAL_STRING: Mutex<String> = Mutex::new(String::new());

fn mutate_global() {
    let mut lock = GLOBAL_STRING.lock().unwrap();
    lock.push_str("Hello, world!");
    // Until we drop the lock, no other thread has access to the global.
}

fn main() {
    mutate_global();
    println!("{}", GLOBAL_STRING.lock().unwrap());
}

Here's how to do it unsafely (avoiding OS overhead). Note that using static mut as described in the top answer is discouraged as it is considered to be overly easy to misuse, but nightly-only SyncUnsafeCell can do the same thing using the "interior mutability" pattern.

The code is now unsafe because if multiple threads call mutate_global() at the same time, they would both hold a &mut reference to the inner String, which is undefined behaviour.

#![feature(sync_unsafe_cell)]
use std::cell::SyncUnsafeCell;

static GLOBAL_STRING: SyncUnsafeCell<String> = SyncUnsafeCell::new(String::new());

fn mutate_global() {
    // SAFETY: No other part of the program can concurrently access `GLOBAL_STRING`.
    let unique_borrow = unsafe { &mut *GLOBAL_STRING.get() };
    unique_borrow.push_str("Hello, world!");
}

fn main() {
    mutate_global();

    // SAFETY: No other part of the program can concurrently write to `GLOBAL_STRING`.
    println!("{}", unsafe { &*GLOBAL_STRING.get() });
}

SyncUnsafeCell is just a wrapper around UnsafeCell that implements Sync, so if you don't have access to the nightly compiler, you can easily reimplement and import it like so:

mod polyfill {
    use core::cell::UnsafeCell;
    use core::ops::Deref;

    pub struct SyncUnsafeCell<T>(UnsafeCell<T>);

    impl<T> SyncUnsafeCell<T> {
        pub const fn new(inner: T) -> Self {
            SyncUnsafeCell(UnsafeCell::new(inner))
        }
    }

    impl<T> Deref for SyncUnsafeCell<T> {
        type Target = UnsafeCell<T>;

        fn deref(&self) -> &Self::Target {
            &self.0
        }
    }

    // This is the important part:
    unsafe impl<T> Sync for SyncUnsafeCell<T> {}
}

use polyfill::SyncUnsafeCell;

Comments

0

The other answers here directly respond to the OP's query, but for anyone looking for help specifically re: safely sharing state in the context of a web server, it is (in my opinion) worth mentioning the amazing web framework actix (I'm not involved with the project aside from benefiting greatly as a user of it).

actix supports "inversion of control" such that state -- like a settings struct that reads from env vars, or a database connection -- can be propagated safely from the top layer of the app down, with downstream handlers etc. simply registering their need for certain state or functionality, basically through defining an input parameter corresponding to the type of that state (axtic refers to this pattern under the term extractors).

For example, consider this web server:

type DbPool = r2d2::Pool<r2d2::ConnectionManager<SqliteConnection>>;

#[actix_web::main]
async fn main() -> io::Result<()> {
    // connect to SQLite DB
    let manager = r2d2::ConnectionManager::<SqliteConnection>::new("app.db");
    let pool = r2d2::Pool::builder()
        .build(manager)
        .expect("database URL should be valid path to SQLite DB file");

    // start HTTP server on port 8080
    HttpServer::new(move || {
        App::new()
            .app_data(web::Data::new(pool.clone()))
            .route("/{name}", web::get().to(index))
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

This line:

.app_data(web::Data::new(pool.clone()))

indicates that there is a database-connection pool available. In a handler, web::Data<T> tells actix to check whatever was set up via .app_data for something matching T, which in this case is DbPool:

async fn index(
    pool: web::Data<DbPool>,
    name: web::Path<(String,)>,
) -> actix_web::Result<impl Responder> {
    ...
}

Here are the docs for database integrations in actix, and here are their more general docs on "extractors." tl;dr for anyone trying to share state in the context of a web server, actix exists and is great for this.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.