我知道通常要避免使用全局变量。尽管如此,我认为在实际意义上,有时需要使用它们(在变量是程序不可或缺的情况下)。
为了学习 Rust,我目前正在使用 sqlite3 和 GitHub 上的 Rust/sqlite3 包编写一个数据库测试程序。因此,这需要(在我的测试程序中)(作为全局变量的替代方案)在大约有十几个函数之间传递数据库变量。下面是一个例子。
在 Rust 中使用全局变量是否可能、可行和可取?鉴于下面的示例,我可以声明和使用全局变量吗?
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) {
| ^^^^
Connection
存储在 Option<Connection>
类型中并尝试将 Option<Connection>
用作 Connection
有关。如果这些错误得到解决(通过使用 Some()
)并且他们使用 unsafe
块,就像他们最初尝试的那样,他们的代码将工作(尽管以线程不安全的方式)。
这是可能的,但不允许直接进行堆分配。堆分配在运行时执行。这里有一些例子:
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,
}
只要静态变量是线程本地的,就可以相当容易地使用它们。
缺点是该对象对您的程序可能产生的其他线程不可见。好处是,与真正的全局状态不同,它是完全安全的,使用起来并不痛苦——真正的全局状态在任何语言中都是一个巨大的痛苦。这是一个例子:
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 ...)
中您可以创建几乎任意对象,包括那些需要堆分配以进行初始化的对象,例如 Vec
、HashMap
等。
如果您不能立即初始化该值,例如它取决于用户输入,您可能还必须在其中抛出 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
});
}
查看 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: Ty
和 static Var: Ty
之间的区别?
const
使全局变量不可变,而 static
使其可变。请注意,对 static
变量的赋值是不安全的。
const
使它成为编译器可以在任何地方内联的值,并且实际上不会在内存中占据固定位置。 (它可能没有被链接器放在任何地方。)并且 static
不会使其可变。使它可变的是mut
。至少,如果我理解正确的话……我学习 Rust 才几天。
我是 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()));
}
另一种解决方案是将交叉波束通道发送/接收对声明为不可变的全局变量。通道应该是有界的,并且只能容纳一个元素。初始化全局变量时,将全局实例推送到通道中。使用全局变量时,弹出通道获取它,使用完后将其推回。
这两种解决方案都应该提供一种使用全局变量的安全方法。
&'static Arc<Mutex<...>>
没有意义,因为它永远不会被破坏,也没有理由克隆它;您可以只使用 &'static Mutex<...>
。
如果您使用 the documentation 中所示的 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");
}
不定期副业成功案例分享
static mut
选项,是否意味着使用连接的每段代码都必须标记为不安全?unsafe
放入宏中会破坏借用检查器的目的。如果您从两个不同的位置可变地触摸db
,您将会出现段错误,最好将unsafe
保留在那里,以便您明确地说“如果我不借用检查自己,我可能会出现段错误”。除非您的宏名称中有“不安全”,否则请继续。