Je sais qu'en général, les variables globales doivent être évitées. Néanmoins, je pense que dans la pratique, il est parfois souhaitable (dans les cas où la variable fait partie intégrante du programme) de les utiliser.
Pour apprendre Rust, je suis en train d’écrire un programme de test de base de données utilisant sqlite3 et le paquet Rust/sqlite3 sur GitHub. Par conséquent, cela nécessite (dans mon programme de test) (comme alternative à une variable globale) de passer la variable de base de données entre des fonctions dont il existe environ une douzaine. Un exemple est ci-dessous.
Est-il possible, réalisable et souhaitable d’utiliser des variables globales dans Rust?
Étant donné l'exemple ci-dessous, puis-je déclarer et utiliser une variable globale?
extern crate sqlite;
fn main() {
let db: sqlite::Connection = open_database();
if !insert_data(&db, insert_max) {
return;
}
}
J'ai essayé ce qui suit, mais cela ne semble pas être tout à fait correct et cela a entraîné les erreurs ci-dessous (j'ai aussi essayé avec un bloc 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),
}
}
Erreurs résultant de la compilation:
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) {
| ^^^^
C'est possible mais aucune allocation de tas n'est autorisée directement. L'allocation de tas est effectuée à l'exécution. Voici quelques exemples:
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,
}
Vous pouvez utiliser des variables statiques assez facilement tant qu'elles sont thread-locales.
L'inconvénient est que l'objet ne sera pas visible par les autres threads que votre programme pourrait générer. L’avantage, c’est que, contrairement à un État véritablement mondial, il est tout à fait sûr et n’est pas pénible à utiliser - un véritable État mondial est une douleur énorme dans toutes les langues. Voici un exemple:
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
});
}
Ici, nous créons une variable statique locale au thread, puis l’utilisons dans une fonction. Notez qu'il est statique et immuable; cela signifie que l'adresse à laquelle il réside est immuable, mais grâce à RefCell
, la valeur elle-même sera modifiable.
Contrairement à static
normal, dans thread-local!(static ...)
, vous pouvez créer des objets plutôt arbitraires, y compris ceux nécessitant des allocations de tas pour l'initialisation, tels que Vec
, HashMap
et autres.
Si vous ne pouvez pas initialiser la valeur immédiatement, par exemple, cela dépend des entrées de l'utilisateur, vous devrez peut-être aussi ajouter Option
, auquel cas, il devient un peu compliqué d'y accéder:
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
});
}
Examinez les sections const
et static
du livre Rust .
Vous pouvez utiliser quelque chose comme suit:
const N: i32 = 5;
ou
static N: i32 = 5;
dans l'espace global.
Mais ce ne sont pas mutables. Pour la mutabilité, vous pouvez utiliser quelque chose comme:
static mut N: i32 = 5;
Puis référencez-les comme:
unsafe {
N += 1;
println!("N: {}", N);
}
Je ne sais pas pourquoi personne ne parle d'une solution qui utilise Arc
. Je suis aussi nouveau à Rust, mais il semble que cette solution fonctionne.
#[marco_use]
extern crate lazy_static;
use std::sync::{Arc, Mutex};
lazy_static! {
static ref GLOBAL: Arc<Mutex<GlobalType> =
Arc::new(Mutex::new(GlobalType::new()));
}
Une autre solution consiste également à déclarer une paire tx/rx de canaux de faisceaux latéraux en tant que variable globale immuable. Le canal doit être délimité et ne peut contenir qu'un seul élément. Lorsque vous initialisez la variable globale, insérez l'instance globale dans le canal. Lorsque vous utilisez la variable globale, ouvrez le canal pour l’acquérir et repoussez-le lorsque vous l’utilisez.
Les deux solutions doivent fournir une approche sans risque pour Rust afin d’utiliser des variables globales.