È possibile utilizzare variabili globali in Rust?


104

So che in generale le variabili globali devono essere evitate. Tuttavia, penso in senso pratico, a volte è auspicabile (in situazioni in cui la variabile è parte integrante del programma) utilizzarli.

Per imparare Rust, sto attualmente scrivendo un programma di test del database usando sqlite3 e il pacchetto Rust / sqlite3 su GitHub. Di conseguenza, ciò richiede (nel mio programma di test) (in alternativa a una variabile globale), di passare la variabile del database tra le funzioni di cui ce ne sono circa una dozzina. Di seguito è riportato un esempio.

  1. È possibile, fattibile e desiderabile utilizzare variabili globali in Rust?

  2. Dato l'esempio seguente, posso dichiarare e utilizzare una variabile globale?

extern crate sqlite;

fn main() {
    let db: sqlite::Connection = open_database();

    if !insert_data(&db, insert_max) {
        return;
    }
}

Ho provato quanto segue, ma non sembra essere del tutto corretto e ha provocato gli errori seguenti (ho provato anche con un unsafeblocco):

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

Errori derivanti dalla compilazione:

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) {
   |              ^^^^

4
Per una soluzione sicura , vedere Come si crea un singleton globale e mutevole? .
Shepmaster

Vorrei sottolineare qui che gli errori che OP sta vivendo hanno a che fare con il tentativo di memorizzare un Connectionall'interno di un Option<Connection>tipo, e cercando di utilizzare un Option<Connection>come Connection. Se quegli errori fossero stati risolti (usando Some()) e usassero un unsafeblocco, come hanno provato originariamente, il loro codice funzionerebbe (anche se in un modo non sicuro per i thread).
TheHansinator

Risposte:


65

È possibile ma non è consentita direttamente l'allocazione dell'heap. L'allocazione dell'heap viene eseguita in fase di esecuzione. Ecco alcuni esempi:

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

13
con l' static mutopzione, significa che ogni pezzo di codice che utilizza la connessione deve essere contrassegnato come non sicuro?
Kamek

1
@Kamek L'accesso iniziale non deve essere sicuro. Di solito uso un involucro sottile di una macro per mascherarlo.
jhpratt

44

È possibile utilizzare variabili statiche abbastanza facilmente purché siano locali di thread.

Lo svantaggio è che l'oggetto non sarà visibile ad altri thread che il tuo programma potrebbe generare. Il lato positivo è che, a differenza dello stato veramente globale, è completamente sicuro e non è un problema da usare: il vero stato globale è un enorme dolore in qualsiasi lingua. Ecco un esempio:

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

Qui creiamo una variabile statica locale del thread e quindi la usiamo in una funzione. Nota che è statico e immutabile; questo significa che l'indirizzo in cui risiede è immutabile, ma grazie al RefCellvalore stesso sarà mutabile.

A differenza del normale static, in thread-local!(static ...)è possibile creare oggetti praticamente arbitrari, inclusi quelli che richiedono allocazioni di heap per l'inizializzazione come Vec, HashMape altri.

Se non è possibile inizializzare immediatamente il valore, ad esempio dipende dall'input dell'utente, potrebbe essere necessario aggiungerlo anche Optionlì, nel qual caso l'accesso diventa un po 'ingombrante:

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

22

Guarda la sezione conste staticdel libro Rust .

Puoi usare qualcosa come segue:

const N: i32 = 5; 

o

static N: i32 = 5;

nello spazio globale.

Ma questi non sono mutabili. Per la mutabilità, potresti usare qualcosa come:

static mut N: i32 = 5;

Quindi fai riferimento a loro come:

unsafe {
    N += 1;

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

1
Si prega di spiegare la differenza tra const Var: Tye static Var: Ty?
Nawaz,

4

Sono nuovo su Rust, ma questa soluzione sembra funzionare:

#[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()));
}

Un'altra soluzione è dichiarare una coppia di canali crossbeam tx / rx come una variabile globale immutabile. Il canale dovrebbe essere limitato e può contenere solo 1 elemento. Quando si inizializza la variabile globale, spingere l'istanza globale nel canale. Quando si utilizza la variabile globale, aprire il canale per acquisirlo e reinserirlo una volta terminato di utilizzarlo.

Entrambe le soluzioni dovrebbero fornire un approccio sicuro all'uso delle variabili globali.


10
Non ha senso &'static Arc<Mutex<...>>perché non può mai essere distrutto e non c'è motivo per clonarlo mai; puoi semplicemente usare &'static Mutex<...>.
trentcl

1

Le allocazioni di heap sono possibili per le variabili statiche se utilizzi la macro lazy_static come mostrato nei documenti

Utilizzando questa macro, è possibile avere statiche che richiedono che il codice venga eseguito in fase di esecuzione per essere inizializzato. Ciò include tutto ciò che richiede allocazioni di heap, come vettori o mappe hash, nonché tutto ciò che richiede il calcolo delle chiamate di funzione.

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

Una risposta esistente parla già di staticità pigra . Si prega di modificare la risposta a dimostrare chiaramente che cosa apprezzi di questa risposta porta rispetto alle risposte esistenti.
Shepmaster,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.