Можно ли в Rust использовать глобальные переменные?

106

Я знаю, что в целом следует избегать глобальных переменных. Тем не менее, я думаю, что с практической точки зрения иногда желательно (в ситуациях, когда переменная является неотъемлемой частью программы) их использовать.

Чтобы изучить Rust, я сейчас пишу программу тестирования базы данных с использованием sqlite3 и пакета Rust / sqlite3 на GitHub. Следовательно, это требует (в моей тестовой программе) (в качестве альтернативы глобальной переменной) передавать переменную базы данных между функциями, которых около дюжины. Пример ниже.

  1. Возможно ли, целесообразно и желательно использовать глобальные переменные в Rust?

  2. В приведенном ниже примере могу ли я объявить и использовать глобальную переменную?

extern crate sqlite;

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

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

Я пробовал следующее, но, похоже, это не совсем правильно и привело к приведенным ниже ошибкам (я пробовал также с unsafeблоком):

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

Ошибки, возникшие в результате компиляции:

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
Чтобы найти безопасное решение, см. Как создать глобальный изменяемый синглтон? .
Shepmaster 05
Здесь я должен отметить, что ошибки, с которыми сталкивается OP, связаны с попыткой сохранить Connectionвнутри Option<Connection>типа и попыткой использовать Option<Connection>в качестве Connection. Если бы эти ошибки были устранены (с помощью Some()) и они использовали unsafeблок, как они изначально пытались, их код работал бы (хотя и небезопасным для потоков способом).
TheHansinator,
Отвечает ли это на ваш вопрос? Как создать глобальный изменяемый синглтон?
испаритель

Ответы:

66

Это возможно, но напрямую не допускается выделение кучи. Выделение кучи выполняется во время выполнения. Вот несколько примеров:

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
с static mutопцией означает ли это, что каждый фрагмент кода, использующий соединение, должен быть помечен как небезопасный?
Kamek
1
@Kamek Первоначальный доступ должен быть небезопасным. Я обычно использую тонкую оболочку макроса, чтобы замаскировать это.
jhpratt
45

Вы можете довольно легко использовать статические переменные, если они являются локальными для потока.

Обратной стороной является то, что объект не будет виден другим потокам, которые может порождать ваша программа. Положительным моментом является то, что в отличие от действительно глобального состояния, оно полностью безопасно и не вызывает боли в использовании - истинное глобальное состояние - огромная боль для любого языка. Вот пример:

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

Здесь мы создаем статическую переменную, локальную для потока, а затем используем ее в функции. Обратите внимание, что он статичен и неизменен; это означает, что адрес, по которому он находится, неизменен, но благодаря самому RefCellзначению будет изменчивым.

В отличие от обычного static, в thread-local!(static ...)вы можете создавать довольно много произвольных объектов, включая те, которые требуют выделения кучи для инициализации, например Vec, HashMapи другие.

Если вы не можете сразу инициализировать значение, например, это зависит от ввода пользователя, вам также может потребоваться Optionдобавить его туда, и в этом случае доступ к нему становится немного громоздким:

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
    });
}
Шнацель
источник
23

Посмотрите раздел constи staticв книге Rust .

Вы можете использовать следующее:

const N: i32 = 5; 

или

static N: i32 = 5;

в глобальном пространстве.

Но они неизменяемы. Для изменчивости вы можете использовать что-то вроде:

static mut N: i32 = 5;

Затем ссылайтесь на них так:

unsafe {
    N += 1;

    println!("N: {}", N);
}
АббасФаисал
источник
1
Объясните, пожалуйста, разницу между const Var: Tyи static Var: Ty?
Наваз
5

Я новичок в Rust, но это решение, похоже, работает:

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

Другое решение - объявить пару tx / rx каналов crossbeam как неизменяемую глобальную переменную. Канал должен быть ограниченным и может содержать только 1 элемент. Когда вы инициализируете глобальную переменную, вставьте глобальный экземпляр в канал. При использовании глобальной переменной нажмите на канал, чтобы получить его, и верните его обратно, когда он будет использоваться.

Оба решения должны обеспечивать безопасный подход к использованию глобальных переменных.

Ифань Сунь
источник
10
В этом нет никакого смысла, &'static Arc<Mutex<...>>потому что он никогда не может быть уничтожен, и нет причин когда-либо его клонировать; вы можете просто использовать &'static Mutex<...>.
trentcl
1

Выделение кучи возможно для статических переменных, если вы используете макрос lazy_static, как показано в документации.

Используя этот макрос, можно иметь статику, которая требует выполнения кода во время выполнения для инициализации. Это включает в себя все, что требует выделения кучи, например векторы или хэш-карты, а также все, что требует вычисления вызовов функций.

// 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
источник
Существующий ответ уже говорит о ленивой статике . Пожалуйста, отредактируйте свой ответ, чтобы четко продемонстрировать ценность этого ответа по сравнению с существующими ответами.
Shepmaster