【问题标题】:Is it possible to use global variables in Rust?是否可以在 Rust 中使用全局变量?
【发布时间】:2013-11-05 11:23:01
【问题描述】:

我知道,一般来说,要避免使用全局变量。尽管如此,我认为在实际意义上,有时(在变量是程序不可或缺的情况下)使用它们是可取的。

为了学习 Rust,我目前正在使用 sqlite3 和 GitHub 上的 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) {
   |              ^^^^

【问题讨论】:

  • 如需安全解决方案,请参阅How do I create a global, mutable singleton?
  • 我应该在这里指出,OP 遇到的错误与尝试将 Connection 存储在 Option&lt;Connection&gt; 类型中,并尝试使用 Option&lt;Connection&gt; 作为 Connection .如果这些错误得到解决(通过使用Some())并且他们使用了unsafe 块,就像他们最初尝试的那样,他们的代码将可以工作(尽管是以线程不安全的方式)。
  • 这能回答你的问题吗? How do I create a global, mutable singleton?

标签: global-variables rust


【解决方案1】:

可以,但不允许直接进行堆分配。堆分配在运行时执行。以下是几个例子:

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

【讨论】:

  • 加上static mut选项,是否意味着每段使用连接的代码都必须被标记为不安全?
  • @Kamek 初始访问必须是不安全的。我通常使用宏的薄包装来掩盖它。
  • @jhpratt 我认为将unsafe 放在宏中会破坏借用检查器的目的。如果您从两个不同的位置可变地触摸db,您将会出现段错误,最好将unsafe 保留在那里,这样您就可以明确地说“如果我不借用检查自己,我可能会出现段错误”。除非您的宏名称中包含“不安全”,否则请继续。
  • 你是绝对正确的@NicholasPipitone。我什至不完全确定两年多前的评论是什么意思。
【解决方案2】:

看看const and static section of the Rust book

你可以使用类似下面的东西:

const N: i32 = 5;

static N: i32 = 5;

在全局空间中。

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

static mut N: i32 = 5;

然后像这样引用它们:

unsafe {
    N += 1;

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

【讨论】:

  • 请解释一下const Var: Tystatic Var: Ty之间的区别?
  • @Nawaz const 使全局变量不可变,而 static 使其可变。请注意,对 static 变量的赋值是不安全的。
【解决方案3】:

您可以相当轻松地使用静态变量,只要它们是线程本地的。

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

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,值本身将是可变的。

与常规的static 不同,在thread-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
    });
}

【讨论】:

  • 您必须使用线程安全编译 sqlite,以便它使用系统(内核)级别的互斥锁。请参阅此处的警告sqlite.org/faq.html#q6 sqlite 包最终会在此处编译 sqlite 本身github.com/stainless-steel/sqlite3-src/blob/master/build.rs 您如何才能真正确定它“完全安全且使用起来不痛苦”?让 1 个线程处理 sqlite 连接和其他线程向该线程发送消息可能会更好,而不是要求 sqlite API 多次打开同一个数据库文件。
【解决方案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()));
}

另一种解决方案是将交叉波束通道发送/接收对声明为不可变的全局变量。通道应该是有界的,并且只能容纳一个元素。初始化全局变量时,将全局实例推送到通道中。使用全局变量时,弹出通道获取,使用完毕后推回。

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

【讨论】:

  • &amp;'static Arc&lt;Mutex&lt;...&gt;&gt; 没有意义,因为它永远不会被销毁,也没有理由克隆它;你可以使用&amp;'static Mutex&lt;...&gt;
【解决方案5】:

如果您使用lazy_static 宏,则静态变量可以进行堆分配,如the documentation 中所示:

使用此宏,可以使用需要在运行时执行代码才能进行初始化的静态变量。这包括任何需要堆分配的东西,例如向量或哈希映射,以及任何需要计算函数调用的东西。

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

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2015-09-27
  • 1970-01-01
  • 2017-03-10
  • 1970-01-01
  • 2014-01-22
  • 2017-12-05
  • 1970-01-01
  • 2023-03-15
相关资源
最近更新 更多