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

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

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

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

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

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.


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

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.