Czy w Rust można używać zmiennych globalnych?

108

Wiem, że generalnie należy unikać zmiennych globalnych. Niemniej jednak uważam, że w sensie praktycznym czasami jest pożądane (w sytuacjach, gdy zmienna jest integralna z programem), aby ich używać.

Aby nauczyć się Rusta, obecnie piszę program do testowania bazy danych przy użyciu sqlite3 i pakietu Rust / sqlite3 na GitHub. W konsekwencji wymaga to (w moim programie testowym) (jako alternatywa dla zmiennej globalnej), aby przekazać zmienną bazy danych pomiędzy funkcjami, których jest ich kilkanaście. Przykład poniżej.

  1. Czy jest możliwe, wykonalne i pożądane używanie zmiennych globalnych w Rust?

  2. Biorąc pod uwagę poniższy przykład, czy mogę zadeklarować i użyć zmiennej globalnej?

extern crate sqlite;

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

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

Próbowałem wykonać następujące czynności, ale nie wydaje się to być całkiem poprawne i spowodowało poniższe błędy (próbowałem również z unsafeblokiem):

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

Błędy wynikające z kompilacji:

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
źródło
4
Aby uzyskać bezpieczne rozwiązanie, zobacz Jak utworzyć globalny, zmienny singleton? .
Shepmaster
Powinienem tutaj zauważyć, że błędy występujące w OP mają związek z próbą przechowywania Connectionwewnątrz Option<Connection>typu i próbą użycia Option<Connection>jako Connection. Jeśli te błędy zostały rozwiązane (przy użyciu Some()) i użyli unsafebloku, tak jak pierwotnie próbowali, ich kod działałby (choć w sposób niebezpieczny dla wątków).
TheHansinator
Czy to odpowiada na twoje pytanie? Jak utworzyć globalny, zmienny singleton?
waporyzator

Odpowiedzi:

66

Jest to możliwe, ale bezpośrednie przydzielanie sterty nie jest dozwolone. Alokacja sterty jest wykonywana w czasie wykonywania. Oto kilka przykładów:

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
źródło
13
z static mutopcją, czy oznacza to, że każdy fragment kodu korzystający z połączenia musi być oznaczony jako niebezpieczny?
Kamek
1
@Kamek Początkowy dostęp musi być niebezpieczny. Zwykle używam cienkiego opakowania makra, aby to zamaskować.
jhpratt
48

Zmiennych statycznych można używać dość łatwo, o ile są one lokalne dla wątku.

Wadą jest to, że obiekt nie będzie widoczny dla innych wątków, które może pojawić się w programie. Plusem jest to, że w przeciwieństwie do prawdziwie globalnego stanu jest całkowicie bezpieczny i nie jest uciążliwy w użyciu - prawdziwy globalny stan jest ogromnym problemem w każdym języku. Oto przykład:

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

Tutaj tworzymy zmienną statyczną lokalną dla wątku, a następnie używamy jej w funkcji. Zauważ, że jest statyczny i niezmienny; oznacza to, że adres pod którym się znajduje jest niezmienny, ale dziękiRefCell samej wartości będzie zmienny.

W przeciwieństwie do zwykłego static, w programie thread-local!(static ...)można tworzyć prawie dowolne obiekty, w tym te, które wymagają alokacji sterty do inicjalizacji, takich jak Vec,HashMap i inne.

Jeśli nie możesz zainicjować wartości od razu, np. Zależy to od danych wejściowych użytkownika, być może będziesz musiał Optiontam również wrzucić , w takim przypadku dostęp do niej staje się nieco nieporęczny:

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
źródło
23

Spójrz na sekcję consti staticsekcję książki Rust .

Możesz użyć czegoś w następujący sposób:

const N: i32 = 5; 

lub

static N: i32 = 5;

w przestrzeni globalnej.

Ale te nie są zmienne. W przypadku zmienności możesz użyć czegoś takiego:

static mut N: i32 = 5;

Następnie odwołaj się do nich w ten sposób:

unsafe {
    N += 1;

    println!("N: {}", N);
}
AbbasFaisal
źródło
1
Proszę wyjaśnić różnicę między const Var: Tyi static Var: Ty?
Nawaz,
6

Jestem nowy w Rust, ale to rozwiązanie wydaje się działać:

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

Innym rozwiązaniem jest zadeklarowanie pary tx / rx kanału belki poprzecznej jako niezmiennej zmiennej globalnej. Kanał powinien być ograniczony i może zawierać tylko 1 element. Kiedy inicjalizujesz zmienną globalną, wypchnij instancję globalną do kanału. Korzystając ze zmiennej globalnej, przesuń kanał, aby go pobrać, i odepchnij go z powrotem po zakończeniu używania.

Oba rozwiązania powinny zapewniać bezpieczne podejście do wykorzystywania zmiennych globalnych.

Yifan Sun
źródło
11
Nie ma sensu, &'static Arc<Mutex<...>>ponieważ nigdy nie można go zniszczyć i nie ma powodu, aby go kiedykolwiek klonować; możesz po prostu użyć &'static Mutex<...>.
trentcl
1

Alokacje sterty są możliwe dla zmiennych statycznych, jeśli używasz makra lazy_static, jak widać w dokumentacji

Korzystając z tego makra, można mieć statystykę, która wymaga wykonania kodu w czasie wykonywania w celu zainicjowania. Obejmuje to wszystko, co wymaga alokacji sterty, takie jak wektory lub mapy skrótów, a także wszystko, co wymaga obliczenia wywołań funkcji.

// 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
źródło
Istniejąca już odpowiedź mówi o leniwej statyczne . Proszę edytować swoje odpowiedzi na jasno wykazują, jaką wartość ta odpowiedź przynosi w porównaniu do istniejących odpowiedzi.
Shepmaster