É possível usar variáveis ​​globais no Rust?

104

Eu sei que, em geral, as variáveis ​​globais devem ser evitadas. No entanto, acho que em um sentido prático, às vezes é desejável (em situações em que a variável é parte integrante do programa) usá-los.

Para aprender Rust, estou atualmente escrevendo um programa de teste de banco de dados usando sqlite3 e o pacote Rust / sqlite3 no GitHub. Conseqüentemente, isso necessita (em meu programa de teste) (como uma alternativa para uma variável global), para passar a variável de banco de dados entre funções das quais existem cerca de uma dúzia. Um exemplo está abaixo.

  1. É possível, viável e desejável usar variáveis ​​globais em Rust?

  2. Dado o exemplo abaixo, posso declarar e usar uma variável global?

extern crate sqlite;

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

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

Tentei o seguinte, mas não parece estar certo e resultou nos erros abaixo (também tentei com um unsafebloco):

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

Erros resultantes da compilação:

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
fonte
4
Para uma solução segura , consulte Como faço para criar um singleton mutável global? .
Shepmaster
Devo observar aqui que os erros que o OP está enfrentando têm a ver com tentar armazenar a Connectiondentro de um Option<Connection>tipo e tentar usar um Option<Connection>como a Connection. Se esses erros fossem resolvidos (usando Some()) e eles usassem um unsafebloco, como tentaram originalmente, seu código funcionaria (embora de maneira insegura para threads).
TheHansinator
Isso responde sua pergunta? Como faço para criar um singleton global mutável?
vaporizador

Respostas:

65

É possível, mas nenhuma alocação de heap é permitida diretamente. A alocação de heap é realizada em tempo de execução. Aqui estão alguns exemplos:

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
fonte
13
com a static mutopção, significa que cada pedaço de código que usa a conexão deve ser marcado como inseguro?
Kamek
1
@Kamek O acesso inicial não pode ser seguro. Eu normalmente uso um invólucro fino de macro para mascarar isso.
jhpratt 01 de
44

Você pode usar variáveis ​​estáticas com bastante facilidade, desde que sejam thread-local.

A desvantagem é que o objeto não ficará visível para outras threads que seu programa possa gerar. A vantagem é que, ao contrário do estado verdadeiramente global, é totalmente seguro e não é difícil de usar - o verdadeiro estado global é uma dor enorme em qualquer idioma. Aqui está um exemplo:

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

Aqui, criamos uma variável estática local de thread e a usamos em uma função. Observe que é estático e imutável; isso significa que o endereço no qual ele reside é imutável, mas graças ao RefCellpróprio valor será mutável.

Ao contrário regulares static, em thread-local!(static ...)que você pode criar objetos praticamente arbitrárias, incluindo aqueles que requerem alocações de heap para a inicialização, como Vec, HashMapentre outros.

Se você não pode inicializar o valor imediatamente, por exemplo, depende da entrada do usuário, você também pode ter que jogar Optionlá, caso em que acessá-lo fica um pouco complicado:

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

Veja a seção conste staticdo livro Rust .

Você pode usar algo da seguinte maneira:

const N: i32 = 5; 

ou

static N: i32 = 5;

no espaço global.

Mas eles não são mutáveis. Para mutabilidade, você poderia usar algo como:

static mut N: i32 = 5;

Em seguida, faça referência a eles como:

unsafe {
    N += 1;

    println!("N: {}", N);
}
AbbasFaisal
fonte
1
Explique a diferença entre const Var: Tye static Var: Ty?
Nawaz,
4

Eu sou novo no Rust, mas esta solução 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()));
}

Outra solução é declarar um par tx / rx de canal de crossbeam como uma variável global imutável. O canal deve ser limitado e pode conter apenas 1 elemento. Ao inicializar a variável global, envie a instância global para o canal. Ao usar a variável global, abra o canal para adquiri-lo e empurre-o de volta quando terminar de usá-lo.

Ambas as soluções devem fornecer uma abordagem segura para o uso de variáveis ​​globais.

Yifan Sun
fonte
10
Não faz sentido &'static Arc<Mutex<...>>porque nunca pode ser destruído e não há razão para cloná-lo; você pode apenas usar &'static Mutex<...>.
trentcl
1

As alocações de heap são possíveis para variáveis ​​estáticas se você usar a macro lazy_static conforme visto nos documentos

Usando esta macro, é possível ter estáticas que requerem que o código seja executado em tempo de execução para ser inicializado. Isso inclui qualquer coisa que requeira alocações de heap, como vetores ou mapas hash, bem como qualquer coisa que requeira o cálculo de chamadas de função.

// 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
fonte
Uma resposta existente já fala sobre estática preguiçosa . Por favor edite sua resposta para claramente demonstrar o que valoriza essa resposta traz em comparação com as respostas existentes.
Shepmaster