Apakah mungkin menggunakan variabel global di Rust?


106

Saya tahu bahwa secara umum, variabel global harus dihindari. Namun demikian, menurut saya dalam arti praktis, kadang-kadang diinginkan (dalam situasi di mana variabel merupakan bagian integral dari program) untuk menggunakannya.

Untuk mempelajari Rust, saat ini saya sedang menulis program pengujian database menggunakan sqlite3 dan paket Rust / sqlite3 di GitHub. Akibatnya, itu mengharuskan (dalam program uji saya) (sebagai alternatif dari variabel global), untuk meneruskan variabel database antara fungsi yang ada sekitar selusin. Contohnya di bawah ini.

  1. Apakah mungkin dan layak dan diinginkan untuk menggunakan variabel global di Rust?

  2. Dengan contoh di bawah ini, dapatkah saya mendeklarasikan dan menggunakan variabel global?

extern crate sqlite;

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

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

Saya mencoba yang berikut ini, tetapi tampaknya kurang tepat dan menghasilkan kesalahan di bawah ini (saya mencoba juga dengan unsafeblok):

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

Kesalahan yang dihasilkan dari kompilasi:

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


Saya harus mencatat di sini bahwa kesalahan yang dialami OP berkaitan dengan mencoba menyimpan Connectiondi dalam Option<Connection>tipe, dan mencoba menggunakan an Option<Connection>sebagai Connection. Jika kesalahan tersebut diselesaikan (dengan menggunakan Some()) dan mereka menggunakan unsafeblok, seperti yang mereka coba semula, kode mereka akan berfungsi (meskipun dengan cara yang tidak aman).
TheHansinator

Jawaban:


66

Itu mungkin tetapi tidak ada alokasi heap yang diizinkan secara langsung. Alokasi heap dilakukan saat runtime. Berikut beberapa contohnya:

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
dengan static mutopsi tersebut, apakah ini berarti bahwa setiap bagian kode yang menggunakan koneksi tersebut harus ditandai sebagai tidak aman?
Kamek

1
@Kamek Akses awal harus tidak aman. Saya biasanya menggunakan pembungkus tipis makro untuk menutupi itu.
jhpratt

45

Anda dapat menggunakan variabel statis dengan cukup mudah asalkan variabel tersebut bersifat lokal-thread.

Kelemahannya adalah objek tidak akan terlihat oleh utas lain yang mungkin muncul dari program Anda. Keuntungannya adalah bahwa tidak seperti keadaan global yang sesungguhnya, ia sepenuhnya aman dan tidak sulit untuk digunakan - keadaan global yang sebenarnya adalah penderitaan yang sangat besar dalam bahasa apa pun. Berikut contohnya:

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

Di sini kita membuat variabel statis thread-local dan kemudian menggunakannya dalam sebuah fungsi. Perhatikan bahwa ini statis dan tidak dapat diubah; ini berarti bahwa alamat tempatnya berada tidak dapat diubah, tetapi berkat RefCellnilainya itu sendiri akan dapat berubah.

Tidak seperti biasa static, thread-local!(static ...)Anda dapat membuat cukup banyak objek arbitrer, termasuk yang membutuhkan alokasi heap untuk inisialisasi seperti Vec, HashMapdan lain-lain.

Jika Anda tidak dapat langsung menginisialisasi nilainya, misalnya tergantung pada input pengguna, Anda mungkin juga harus memasukkannya ke Optionsana, dalam hal ini mengaksesnya akan sedikit sulit:

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

Lihatlah bagian constdan staticdari buku Rust .

Anda dapat menggunakan sesuatu sebagai berikut:

const N: i32 = 5; 

atau

static N: i32 = 5;

di ruang global.

Tapi ini tidak bisa berubah. Untuk mutabilitas, Anda bisa menggunakan sesuatu seperti:

static mut N: i32 = 5;

Kemudian rujuk mereka seperti:

unsafe {
    N += 1;

    println!("N: {}", N);
}

1
Tolong jelaskan perbedaan antara const Var: Tydan static Var: Ty?
Nawaz

5

Saya baru mengenal Rust, tetapi solusi ini sepertinya berhasil:

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

Solusi lain adalah mendeklarasikan pasangan saluran silang tx / rx sebagai variabel global yang tidak dapat diubah. Saluran harus dibatasi dan hanya dapat menampung 1 elemen. Saat Anda menginisialisasi variabel global, dorong instance global ke dalam saluran. Saat menggunakan variabel global, pop saluran untuk memperolehnya dan dorong kembali setelah selesai menggunakannya.

Kedua solusi tersebut harus memberikan pendekatan yang aman untuk menggunakan variabel global.


10
Tidak ada gunanya &'static Arc<Mutex<...>>karena itu tidak akan pernah bisa dihancurkan dan tidak ada alasan untuk mengkloningnya; Anda bisa menggunakan &'static Mutex<...>.
trentcl

1

Alokasi heap dimungkinkan untuk variabel statis jika Anda menggunakan makro lazy_static seperti yang terlihat di dokumen

Menggunakan makro ini, dimungkinkan untuk memiliki statika yang memerlukan kode untuk dieksekusi pada waktu proses untuk diinisialisasi. Ini mencakup semua yang membutuhkan alokasi heap, seperti vektor atau peta hash, serta apa pun yang memerlukan pemanggilan fungsi untuk dihitung.

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

Sebuah jawaban yang ada sudah berbicara tentang statis malas . Harap edit jawaban Anda untuk menunjukkan dengan jelas nilai apa yang diberikan jawaban ini dibandingkan dengan jawaban yang ada.
Shepmaster
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.