ChatGPT解决这个技术问题 Extra ChatGPT

Is it possible to use global variables in Rust?

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.

Is it possible and feasible and desirable to use global variables in Rust? 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("Error opening test.db");
    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("Error opening test.db");
  |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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) {
   |              ^^^^
For a safe solution, please see How do I create a global, mutable singleton?.
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).
Does this answer your question? How do I create a global, mutable singleton?

P
Peter Mortensen

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,
}

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.
c
cloudcalvin

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
    });
}

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.
P
Peter Mortensen

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);
}

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).
P
Peter Mortensen

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.


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<...>.
P
Peter Mortensen

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");
}

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.
J
Jermyn Xia

Use crate once_cell or lazy_static, or SyncOnceCell in nightly.