是否可以在Rust中使用全局变量?


104

我知道总体上应该避免全局变量。尽管如此,我认为从实际意义上讲,有时(在变量是程序不可或缺的情况下)使用它们是可取的。

为了学习Rust,我目前正在GitHub上使用sqlite3和Rust / sqlite3包编写一个数据库测试程序。因此,这(在我的测试程序中)有必要(作为全局变量的替代方法)在大约十二个函数之间传递数据库变量。下面是一个示例。

  1. 在Rust中使用全局变量是否可行,可行和可取?

  2. 给定以下示例,我可以声明和使用全局变量吗?

extern crate sqlite;

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

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

我尝试了以下操作,但似乎不太正确,并导致了以下错误(我也尝试了一个unsafe块):

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

编译导致的错误:

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) {
   |              ^^^^

4
为了获得安全的解决方案,请参阅如何创建全局的可变单例?
Shepmaster

我在这里应该注意,OP遇到的错误与尝试将一个Connection内部Option<Connection>类型存储并尝试将an Option<Connection>用作a有关Connection。如果这些错误已解决(通过使用Some()),并且unsafe如最初尝试的那样使用了一个块,则其代码将起作用(尽管以线程不安全的方式)。
TheHansinator

这回答了你的问题了吗?如何创建全局的可变单例?
蒸发器

Answers:


65

有可能,但是不允许直接分配堆。堆分配在运行时执行。这里有一些例子:

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

13
使用该static mut选项,是否意味着使用该连接的每段代码都必须标记为不安全?
卡梅克

1
@Kamek初始访问必须不安全。我通常使用宏的薄包装来掩盖它。
jhpratt

44

您可以轻松地使用静态变量,只要它们是线程局部的即可。

缺点是该对象对您的程序可能产生的其他线程不可见。好处是,它与真正的全球状态不同,它是完全安全的,并且使用起来并不痛苦-真正的全局状态在任何语言中都是一个巨大的痛苦。这是一个例子:

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

在这里,我们创建一个线程局部静态变量,然后在函数中使用它。请注意,它是静态且不可变的。这意味着它所在的地址是不可变的,但是由于RefCell值本身是可变的。

与normal不同staticthread-local!(static ...)您可以在其中创建几乎任意的对象,包括需要堆分配进行初始化的对象,例如VecHashMap和其他人。

如果您不能立即初始化该值,例如,它取决于用户输入,则可能还必须扔Option在那里,在这种情况下,访问它会有些笨拙:

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

22

查看Rust书conststatic部分

您可以使用以下方法:

const N: i32 = 5; 

要么

static N: i32 = 5;

在全球范围内。

但是这些不是可变的。对于可变性,您可以使用类似以下内容的方法:

static mut N: i32 = 5;

然后像这样引用它们:

unsafe {
    N += 1;

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

1
请不要解释之间的差异const Var: Tystatic Var: Ty
纳瓦兹

4

我是Rust的新手,但此解决方案似乎有效:

#[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()));
}

另一种解决方案是将跨波束通道tx / rx对声明为不可变的全局变量。通道应该有界,并且只能容纳1个元素。初始化全局变量时,将全局实例推入通道。使用全局变量时,弹出通道以获取它,并在使用完后将其推回去。

两种解决方案都应提供使用全局变量的安全方法。


10
没有任何意义,&'static Arc<Mutex<...>>因为它永远无法销毁,也没有理由克隆它。您可以使用&'static Mutex<...>
trentcl

1

如果您使用docs中所示的lazy_static宏,则可以为静态变量进行堆分配

使用此宏,可能需要一些静态函数,这些静态函数要求在运行时执行代码才能进行初始化。这包括需要堆分配的所有内容,例如向量或哈希图,以及需要计算函数调用的所有内容。

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

现有的答案已经谈到懒静态。请修改您的答案,以清楚地证明此答案与现有答案相比具有什么价值。
Shepmaster
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.