Ist es möglich, globale Variablen in Rust zu verwenden?


104

Ich weiß, dass globale Variablen generell vermieden werden müssen. Dennoch denke ich im praktischen Sinne, dass es manchmal wünschenswert ist (in Situationen, in denen die Variable ein integraler Bestandteil des Programms ist), sie zu verwenden.

Um Rust zu lernen, schreibe ich derzeit ein Datenbanktestprogramm mit sqlite3 und dem Rust / sqlite3-Paket auf GitHub. Folglich erfordert dies (in meinem Testprogramm) (als Alternative zu einer globalen Variablen), die Datenbankvariable zwischen Funktionen zu übergeben, von denen es ungefähr ein Dutzend gibt. Ein Beispiel ist unten.

  1. Ist es möglich und machbar und wünschenswert, globale Variablen in Rust zu verwenden?

  2. Kann ich im folgenden Beispiel eine globale Variable deklarieren und verwenden?

extern crate sqlite;

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

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

Ich habe Folgendes versucht, aber es scheint nicht ganz richtig zu sein und führte zu den folgenden Fehlern (ich habe es auch mit einem unsafeBlock versucht ):

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

Fehler beim Kompilieren:

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


Ich sollte hier beachten, dass die Fehler, die bei OP auftreten, mit dem Versuch zu tun haben, einen Connectionin einem Option<Connection>Typ zu speichern und einen Option<Connection>als zu verwenden Connection. Wenn diese Fehler behoben würden (mithilfe von Some()) und sie einen unsafeBlock verwenden würden, wie sie ursprünglich versucht hatten, würde ihr Code funktionieren (wenn auch auf eine thread-unsichere Weise).
TheHansinator

Antworten:


65

Es ist möglich, aber keine direkte Heap-Zuordnung zulässig. Die Heap-Zuweisung erfolgt zur Laufzeit. Hier einige Beispiele:

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 mutBedeutet dies bei dieser Option, dass jeder Code, der die Verbindung verwendet, als unsicher markiert werden muss?
Kamek

1
@Kamek Der anfängliche Zugriff muss unsicher sein. Ich benutze normalerweise einen dünnen Wrapper eines Makros, um das zu maskieren.
jhpratt

44

Sie können statische Variablen ziemlich einfach verwenden, solange sie threadlokal sind.

Der Nachteil ist, dass das Objekt für andere Threads, die Ihr Programm möglicherweise erzeugt, nicht sichtbar ist. Der Vorteil ist, dass es im Gegensatz zu einem wirklich globalen Staat völlig sicher ist und kein Schmerz ist - ein wahrer globaler Staat ist ein massiver Schmerz in jeder Sprache. Hier ist ein Beispiel:

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

Hier erstellen wir eine threadlokale statische Variable und verwenden sie dann in einer Funktion. Beachten Sie, dass es statisch und unveränderlich ist. Dies bedeutet, dass die Adresse, an der es sich befindet, unveränderlich ist, aber dank RefCelldes Werts selbst veränderbar ist.

Im Gegensatz zu herkömmlichen static, in thread-local!(static ...)können Sie ziemlich beliebige Objekte erstellen, einschließlich denen, die für die Initialisierung Heapzuweisungen erfordern wie Vec, HashMapund andere.

Wenn Sie den Wert nicht sofort initialisieren können, z. B. abhängig von Benutzereingaben, müssen Sie möglicherweise auch einen Wert eingeben Option. In diesem Fall wird der Zugriff etwas unhandlich:

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

Schauen Sie sich den Abschnitt constund staticim Rust-Buch an .

Sie können Folgendes verwenden:

const N: i32 = 5; 

oder

static N: i32 = 5;

im globalen Raum.

Diese sind aber nicht veränderlich. Für die Veränderlichkeit könnten Sie Folgendes verwenden:

static mut N: i32 = 5;

Dann verweise sie wie folgt:

unsafe {
    N += 1;

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

1
Bitte erklären Sie den Unterschied zwischen const Var: Tyund static Var: Ty?
Nawaz

4

Ich bin neu bei Rust, aber diese Lösung scheint zu funktionieren:

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

Eine andere Lösung besteht darin, ein Crossbeam-Kanal-TX / RX-Paar als unveränderliche globale Variable zu deklarieren. Der Kanal sollte begrenzt sein und kann nur 1 Element enthalten. Wenn Sie die globale Variable initialisieren, verschieben Sie die globale Instanz in den Kanal. Wenn Sie die globale Variable verwenden, öffnen Sie den Kanal, um ihn zu erfassen, und drücken Sie ihn zurück, wenn Sie ihn nicht mehr verwenden.

Beide Lösungen sollten einen sicheren Ansatz für die Verwendung globaler Variablen bieten.


10
Es hat keinen Sinn, &'static Arc<Mutex<...>>weil es niemals zerstört werden kann und es keinen Grund gibt, es jemals zu klonen. Sie können nur verwenden &'static Mutex<...>.
Trentcl

1

Heap-Zuweisungen sind für statische Variablen möglich, wenn Sie das Makro lazy_static verwenden, wie in den Dokumenten gezeigt

Mit diesem Makro können Statiken erstellt werden, bei denen zur Laufzeit Code ausgeführt werden muss, um initialisiert zu werden. Dies umfasst alles, was Heap-Zuweisungen erfordert, wie Vektoren oder Hash-Maps, sowie alles, was die Berechnung von Funktionsaufrufen erfordert.

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

Eine vorhandene Antwort spricht bereits von fauler statischer Aufladung . Bitte bearbeiten Sie Ihre Antwort, um deutlich zu machen, welchen Wert diese Antwort im Vergleich zu den vorhandenen Antworten hat.
Shepmaster
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.