221 votes

Est-il possible d'utiliser des variables globales en Rust ?

Je sais qu'en général, il est recommandé d'éviter les variables globales. Néanmoins, je pense qu'en pratique, il est parfois souhaitable (dans des situations où la variable est essentielle pour le programme) de les utiliser.

Pour apprendre Rust, j'écris actuellement un programme de test de base de données en utilisant sqlite3 et le package Rust/sqlite3 sur GitHub. En conséquence, cela nécessite (dans mon programme de test) (comme alternative à une variable globale), de passer la variable de base de données entre les fonctions dont il y en a environ une douzaine. Un exemple est donné ci-dessous.

  1. Est-il possible, réalisable et souhaitable d'utiliser des variables globales en Rust ?

  2. É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 a donné les erreurs ci-dessous (j'ai également essayé avec un bloc unsafe) :

extern crate sqlite;

static mut DB: Option = None;

fn main() {
    DB = sqlite::open("test.db").expect("Erreur à l'ouverture de test.db");
    println!("Base de données ouverte avec succès");

    create_table();
    println!("Terminé");
}

// Créer la 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 créée"),
        Err(err) => println!("Exécution du SQL échouée : {}\nSql={}", err, sql),
    }
}

Erreurs résultant de la compilation :

erreur[E0308]: types incompatibles
 --> src/main.rs:6:10
  |
6 |     DB = sqlite::open("test.db").expect("Erreur à l'ouverture de test.db");
  |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type attendue `std::option::Option`, trouvé `struct sqlite::Connection`
  |
  = note: type attendue `std::option::Option`
             type trouvée `sqlite::Connection`

erreur: aucune méthode nommée `exec` trouvée pour le type `std::option::Option` dans la portée actuelle
  --> src/main.rs:16:14
   |
16 |     match DB.exec(sql) {
   |              ^^^^

119voto

Ercan Erden Points 476

Il est possible, mais l'allocation sur le tas n'est pas autorisée directement. L'allocation sur le tas est effectuée à l'exécution. Voici quelques exemples :

static SOME_INT: i32 = 5;
static SOME_STR: &'static str = "Une chaîne statique";
static SOME_STRUCT: MyStruct = MyStruct {
    number: 10,
    string: "Quelque chaîne",
};
static mut db: Option = Aucun;

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

88voto

Shnatsel Points 1114

Vous pouvez utiliser des variables statiques assez facilement tant qu'elles sont thread-local.

L'inconvénient est que l'objet ne sera pas visible pour les autres threads que votre programme pourrait créer. L'avantage est que contrairement à un état véritablement global, il est entièrement sûr et n'est pas contraignant à utiliser - un état véritablement global est un véritable casse-tête dans n'importe quel langage. Voici un exemple:

extern mod sqlite;

use std::cell::RefCell;

thread_local!(static ODB: RefCell = RefCell::new(sqlite::open("test.db"));

fn main() {
    ODB.with(|odb_cell| {
        let odb = odb_cell.borrow_mut();
        // le code qui utilise odb va ici
    });
}

Ici, nous créons une variable statique thread-local et l'utilisons dans une fonction. Remarquez qu'elle est statique et immuable; cela signifie que l'adresse à laquelle elle réside est immuable, mais grâce à RefCell, la valeur elle-même sera mutable.

Contrairement à static classique, dans thread-local!(static ...), vous pouvez créer à peu près n'importe quel objet, y compris ceux qui nécessitent des allocations sur le tas pour l'initialisation comme Vec, HashMap et d'autres.

Si vous ne pouvez pas initialiser la valeur immédiatement, par exemple si elle dépend de l'entrée de l'utilisateur, vous devrez peut-être également utiliser Option, auquel cas y accéder devient un peu maladroit:

extern mod sqlite;

use std::cell::RefCell;

thread_local!(static ODB: RefCell> = RefCell::New(None));

fn main() {
    ODB.with(|odb_cell| {
        // suppose que la valeur a déjà été initialisée, panique sinon
        let odb = odb_cell.borrow_mut().as_mut().unwrap();
        // le code qui utilise odb va ici
    });
}

39voto

AbbasFaisal Points 557

Regardez la const et static section du livre sur Rust.

Vous pouvez utiliser quelque chose comme ceci :

const N: i32 = 5;

ou

static N: i32 = 5;

dans l'espace global.

Mais ceux-ci ne sont pas mutables. Pour la mutabilité, vous pourriez utiliser quelque chose comme :

static mut N: i32 = 5;

Ensuite, les référencer comme suit :

unsafe {
    N += 1;

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

22voto

Yifan Sun Points 577

Je suis nouveau sur Rust, mais cette solution semble fonctionner :

#[macro_use]
extern crate lazy_static;

use std::sync::{Arc, Mutex};

lazy_static! {
    static ref GLOBAL: Arc =
        Arc::new(Mutex::new(GlobalType::new()));
}

Une autre solution consiste à déclarer une paire de transmission/réception de canal crossbeam en tant que variable globale immuable. Le canal devrait être borné et ne peut contenir qu'un seul élément. Lors de l'initialisation de la variable globale, poussez l'instance globale dans le canal. Lors de l'utilisation de la variable globale, retirez-la du canal pour l'acquérir et repoussez-la une fois que vous avez fini de l'utiliser.

Les deux solutions devraient fournir une approche sûre pour l'utilisation de variables globales.

19voto

R34lthing Points 160

Les allocations en tas sont possibles pour les variables statiques si vous utilisez la macro lazy_static telle que vue dans la documentation:

En utilisant cette macro, il est possible d'avoir des variables statiques qui nécessitent l'exécution de code au moment de l'exécution pour être initialisées. Cela inclut tout ce qui nécessite des allocations en tas, comme des vecteurs ou des cartes de hachage, ainsi que tout ce qui nécessite des appels de fonction pour être calculé.

// Déclare une constante HashMap évaluée de manière paresseuse. La HashMap sera évaluée une fois et
// stockée derrière une référence statique globale.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X