¿Es posible usar variables globales en Rust?

104

Sé que, en general, se deben evitar las variables globales. Sin embargo, creo que en un sentido práctico, a veces es deseable (en situaciones en las que la variable es parte integral del programa) utilizarlos.

Para aprender Rust, actualmente estoy escribiendo un programa de prueba de base de datos usando sqlite3 y el paquete Rust / sqlite3 en GitHub. En consecuencia, eso requiere (en mi programa de prueba) (como alternativa a una variable global), pasar la variable de la base de datos entre funciones de las cuales hay alrededor de una docena. A continuación se muestra un ejemplo.

  1. ¿Es posible, factible y deseable utilizar variables globales en Rust?

  2. Dado el siguiente ejemplo, ¿puedo declarar y usar una variable global?

extern crate sqlite;

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

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

Intenté lo siguiente, pero no parece ser del todo correcto y resultó en los siguientes errores (también probé con un unsafebloque):

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

Errores que resultaron de la compilación:

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
fuente
4
Para obtener una solución segura , consulte ¿Cómo puedo crear un singleton mutable global? .
Shepmaster
Debo señalar aquí que los errores que está experimentando OP tienen que ver con intentar almacenar Connectionun Option<Connection>tipo dentro de un tipo y tratar de usar un Option<Connection>como Connection. Si esos errores se resolvieron (usando Some()) y usaron un unsafebloque, como lo intentaron originalmente, su código funcionaría (aunque de una manera insegura para el subproceso).
TheHansinator
¿Responde esto a tu pregunta? ¿Cómo creo un singleton global mutable?
vaporizador

Respuestas:

65

Es posible, pero no se permite la asignación de montón directamente. La asignación de montón se realiza en tiempo de ejecución. Aquí están algunos ejemplos:

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
fuente
13
con la static mutopción, ¿significa que cada fragmento de código que usa la conexión debe marcarse como inseguro?
Kamek
1
@Kamek El acceso inicial debe ser inseguro. Normalmente uso un envoltorio delgado de una macro para enmascarar eso.
jhpratt
44

Puede usar variables estáticas con bastante facilidad siempre que sean locales de subproceso.

La desventaja es que el objeto no será visible para otros hilos que su programa pueda generar. La ventaja es que, a diferencia del estado verdaderamente global, es completamente seguro y no es una molestia usarlo; el verdadero estado global es una molestia enorme en cualquier idioma. He aquí un ejemplo:

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

Aquí creamos una variable estática local de hilo y luego la usamos en una función. Tenga en cuenta que es estático e inmutable; esto significa que la dirección en la que reside es inmutable, pero gracias al RefCellvalor en sí será mutable.

A diferencia de lo normal static, en thread-local!(static ...)puede crear objetos bastante arbitrarios, incluidos aquellos que requieren asignaciones de montón para la inicialización, como Vec, HashMapy otros.

Si no puede inicializar el valor de inmediato, por ejemplo, depende de la entrada del usuario, es posible que también deba agregarlo Option, en cuyo caso acceder a él se vuelve un poco difícil de manejar:

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
fuente
22

Mira la sección consty staticdel libro de Rust .

Puede usar algo de la siguiente manera:

const N: i32 = 5; 

o

static N: i32 = 5;

en el espacio global.

Pero estos no son mutables. Para la mutabilidad, podría usar algo como:

static mut N: i32 = 5;

Luego haga referencia a ellos como:

unsafe {
    N += 1;

    println!("N: {}", N);
}
AbbasFaisal
fuente
1
Explique la diferencia entre const Var: Tyy static Var: Ty?
Nawaz
4

Soy nuevo en Rust, pero esta solución parece funcionar:

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

Otra solución es declarar un par tx / rx de canal transversal como una variable global inmutable. El canal debe estar delimitado y solo puede contener 1 elemento. Cuando inicialice la variable global, inserte la instancia global en el canal. Cuando use la variable global, haga estallar el canal para adquirirlo y empújelo hacia atrás cuando termine de usarlo.

Ambas soluciones deben proporcionar un enfoque seguro para el uso de variables globales.

Yifan Sun
fuente
10
No tiene sentido &'static Arc<Mutex<...>>porque nunca se puede destruir y no hay razón para clonarlo; puedes usar &'static Mutex<...>.
trentcl
1

Las asignaciones de montón son posibles para variables estáticas si usa la macro lazy_static como se ve en los documentos

Usando esta macro, es posible tener estática que requiera que el código se ejecute en tiempo de ejecución para poder inicializarse. Esto incluye todo lo que requiera asignaciones de montón, como vectores o mapas hash, así como todo lo que requiera que se calculen llamadas a funciones.

// 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
fuente
Una respuesta existente ya habla de estática perezosa . Por favor, editar su respuesta para demostrar claramente qué valor aporta esta respuesta en comparación con las respuestas existentes.
Shepmaster