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

104

Je sais qu'en général, les variables globales doivent être évitées. Néanmoins, je pense que dans un sens pratique, il est parfois souhaitable (dans des situations où la variable fait partie intégrante du programme) de les utiliser.

Afin d'apprendre Rust, j'écris actuellement un programme de test de base de données utilisant sqlite3 et le package 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 y en a une dizaine. Un exemple est ci-dessous.

  1. Est-il possible, faisable et souhaitable d'utiliser des variables globales dans Rust?

  2. Compte tenu de 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 tout à fait correct et a entraîné les erreurs ci-dessous (j'ai également essayé avec un unsafebloc):

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) {
   |              ^^^^
Brian Oh
la source
4
Pour une solution sûre , consultez Comment créer un singleton global et mutable? .
Shepmaster
Je dois noter ici que les erreurs rencontrées par OP sont liées à la tentative de stockage d'un type à l' Connectionintérieur d'un Option<Connection>type et à l'utilisation d'un Option<Connection>fichier Connection. Si ces erreurs étaient résolues (en utilisant Some()) et qu'ils utilisaient un unsafebloc, comme ils l'ont essayé à l'origine, leur code fonctionnerait (bien que d'une manière non sécurisée pour les threads).
TheHansinator
Est-ce que cela répond à votre question? Comment créer un singleton global et mutable?
vaporisateur

Réponses:

65

C'est possible mais aucune allocation de tas n'est autorisée directement. L'allocation de tas est effectuée au moment de 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,
}
Ercan Erden
la source
13
avec l' static mutoption, cela signifie-t-il que chaque morceau de code qui utilise la connexion doit être marqué comme dangereux?
Kamek
1
@Kamek L'accès initial doit être dangereux. J'utilise généralement un fin wrapper d'une macro pour masquer cela.
jhpratt
44

Vous pouvez utiliser des variables statiques assez facilement tant qu'elles sont locales au thread.

L'inconvénient est que l'objet ne sera pas visible pour les autres threads que votre programme pourrait générer. L'avantage est que contrairement à un état véritablement mondial, il est entièrement sûr et n'est pas pénible à utiliser - le véritable état mondial est une douleur énorme dans n'importe quelle langue. 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 nous 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 à RefCellla valeur elle-même sera mutable.

Contrairement à la norme static, thread-local!(static ...)vous pouvez créer des objets à peu près arbitraires, y compris ceux qui nécessitent des allocations de tas pour l'initialisation, tels que Vec, HashMapet d'autres.

Si vous ne pouvez pas initialiser la valeur tout de suite, par exemple cela dépend de l'entrée de l'utilisateur, vous devrez peut-être également y ajouter Option, auquel cas y accéder devient un peu compliqué:

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
    });
}
Shnatsel
la source
22

Regardez la section constet staticdu livre Rust .

Vous pouvez utiliser quelque chose comme suit:

const N: i32 = 5; 

ou

static N: i32 = 5;

dans l'espace mondial.

Mais ceux-ci ne sont pas modifiables. 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);
}
AbbasFaisal
la source
1
Veuillez expliquer la différence entre const Var: Tyet static Var: Ty?
Nawaz du
4

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<Mutex<GlobalType> =
        Arc::new(Mutex::new(GlobalType::new()));
}

Une autre solution consiste à déclarer une paire tx / rx de canal crossbeam en tant que variable globale immuable. Le canal doit être borné et ne peut contenir qu'un seul élément. Lorsque vous initialisez la variable globale, poussez l'instance globale dans le canal. Lorsque vous utilisez la variable globale, ouvrez le canal pour l'acquérir et repoussez-le lorsque vous avez terminé de l'utiliser.

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

Yifan Sun
la source
10
&'static Arc<Mutex<...>>Cela ne sert à rien car il ne peut jamais être détruit et il n'y a aucune raison de jamais le cloner; vous pouvez simplement utiliser &'static Mutex<...>.
trentcl
1

Les allocations de tas sont possibles pour les variables statiques si vous utilisez la macro lazy_static comme indiqué dans la documentation

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

// 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");
}
SanBen
la source
Une réponse existante parle déjà de statique paresseux . Veuillez modifier votre réponse pour démontrer clairement la valeur que cette réponse apporte par rapport aux réponses existantes.
Shepmaster