一般的に、グローバル変数は避けるべきであることを知っています。それにもかかわらず、私は実用的な意味で、それらを使用することが望ましい場合があります(変数がプログラムに不可欠な状況で)。
Rustを学ぶために、現在GitHubでsqlite3とRust/sqlite3パッケージを使用してデータベーステストプログラムを作成しています。そのため、(私のテストプログラムでは)(グローバル変数の代替として)約12の機能間でデータベース変数を渡す必要があります。以下に例を示します。
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) {
| ^^^^
可能ですが、ヒープの割り当ては直接許可されていません。ヒープ割り当ては実行時に実行されます。以下に例を示します。
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
});
}
Rust本の const
およびstatic
セクション を見てください。
次のように使用できます。
const N: i32 = 5;
または
static N: i32 = 5;
グローバルスペースで。
しかし、これらは可変ではありません。可変性のために、次のようなものを使用できます。
static mut N: i32 = 5;
次に、それらを次のように参照します。
unsafe {
N += 1;
println!("N: {}", N);
}
Arc
を使用するソリューションについて誰も話さない理由がわかりません。私も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つの要素しか保持できません。グローバル変数を初期化するときに、グローバルインスタンスをチャネルにプッシュします。グローバル変数を使用する場合、チャネルをポップして取得し、使用が完了したらプッシュバックします。
両方のソリューションは、グローバル変数を使用するためのRust安全なアプローチを提供する必要があります。