Чи можна використовувати глобальні змінні в Rust?


104

Я знаю, що загалом слід уникати глобальних змінних. Тим не менше, я думаю, що в практичному сенсі іноді бажано (у ситуаціях, коли змінна є невід'ємною частиною програми) використовувати їх.

Для того, щоб вивчити 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) {
   |              ^^^^


Тут слід зауважити, що помилки, які виникають у OP, пов’язані зі спробою зберегти Connectionусередині Option<Connection>типу та спробою використовувати Option<Connection>як Connection. Якби ці помилки були усунені (за допомогою Some()), і вони використовували unsafeблок, як це було спочатку, їх код працював би (хоч і небезпечно для потоку).
TheHansinator

Чи відповідає це на ваше запитання? Як створити глобальний, змінний синглтон?
випарник

Відповіді:


65

Це можливо, але розподіл купи не дозволяється безпосередньо. Розподіл купи виконується під час виконання. Ось кілька прикладів:

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опцією, чи означає це, що кожен фрагмент коду, який використовує з'єднання, повинен бути позначений як небезпечний?
Камек

1
@Kamek Початковий доступ повинен бути небезпечним. Я зазвичай використовую тонку обгортку макросу, щоб замаскувати це.
jhpratt

44

Ви можете використовувати статичні змінні досить легко, якщо вони є локальними.

Недоліком є ​​те, що об’єкт не буде видимим для інших потоків, які може виникнути у вашій програмі. Перевагою є те, що на відміну від справді глобальної держави, вона цілком безпечна і не болюча для використання - справжня глобальна держава є величезною болем будь-якої мови. Ось приклад:

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

22

Подивіться на constі staticрозділ книги «Іржа» .

Ви можете використовувати щось наступне:

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?
Nawaz,

4

Я новачок у 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 поперечного променя як незмінну глобальну змінну. Канал повинен бути обмеженим і містити лише 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");
}

Існуючий відповідь вже говорить про ліниву статиці . Будь ласка, відредагуйте свою відповідь, щоб чітко продемонструвати, яку цінність ця відповідь дає порівняно з існуючими відповідями.
Шепмастер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.