Có thể sử dụng các biến toàn cục trong Rust không?


104

Tôi biết rằng nói chung, cần tránh các biến toàn cục. Tuy nhiên, tôi nghĩ theo nghĩa thực tế, đôi khi người ta mong muốn (trong các tình huống mà biến là tích phân của chương trình) để sử dụng chúng.

Để học Rust, tôi hiện đang viết một chương trình kiểm tra cơ sở dữ liệu sử dụng sqlite3 và gói Rust / sqlite3 trên GitHub. Do đó, điều đó đòi hỏi (trong chương trình thử nghiệm của tôi) (như một sự thay thế cho một biến toàn cục), để chuyển biến cơ sở dữ liệu giữa các hàm trong đó có khoảng một tá hàm. Dưới đây là một ví dụ.

  1. Việc sử dụng các biến toàn cục trong Rust có khả thi và khả thi không?

  2. Với ví dụ dưới đây, tôi có thể khai báo và sử dụng một biến toàn cục không?

extern crate sqlite;

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

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

Tôi đã thử các cách sau, nhưng nó có vẻ không đúng và dẫn đến các lỗi bên dưới (tôi cũng đã thử với một unsafekhối):

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

Lỗi do biên dịch:

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


Tôi nên lưu ý ở đây rằng các lỗi mà OP đang gặp phải liên quan đến việc cố gắng lưu trữ Connectionbên trong một Option<Connection>loại và cố gắng sử dụng một Option<Connection>dưới dạng Connection. Nếu những lỗi đó được giải quyết (bằng cách sử dụng Some()) và họ sử dụng một unsafekhối, như họ đã thử ban đầu, thì mã của họ sẽ hoạt động (mặc dù theo cách không an toàn theo luồng).
TheHansinator

Điều này có trả lời câu hỏi của bạn không? Làm cách nào để tạo một singleton toàn cầu, có thể thay đổi được?
hơi

Câu trả lời:


65

Nó có thể nhưng không được phép phân bổ heap trực tiếp. Phân bổ đống được thực hiện trong thời gian chạy. Đây là vài ví dụ:

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
với static muttùy chọn, nó có nghĩa là mọi đoạn mã sử dụng kết nối phải được đánh dấu là không an toàn?
Kamek

1
@Kamek Quyền truy cập ban đầu phải không an toàn. Tôi thường sử dụng một lớp bao bọc mỏng của macro để che đi điều đó.
jhpratt

44

Bạn có thể sử dụng các biến tĩnh khá dễ dàng miễn là chúng là một chuỗi cục bộ.

Nhược điểm là đối tượng sẽ không hiển thị với các luồng khác mà chương trình của bạn có thể sinh ra. Ưu điểm là không giống như trạng thái toàn cầu thực sự, nó hoàn toàn an toàn và không gây khó khăn khi sử dụng - trạng thái toàn cầu thực sự là một nỗi đau lớn trong bất kỳ ngôn ngữ nào. Đây là một ví dụ:

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

Ở đây chúng ta tạo một biến static thread-local và sau đó sử dụng nó trong một hàm. Lưu ý rằng nó là tĩnh và bất biến; điều này có nghĩa là địa chỉ mà nó cư trú là bất biến, nhưng nhờ RefCellbản thân giá trị sẽ có thể thay đổi được.

Không giống như bình thường static, trong thread-local!(static ...)bạn có thể tạo khá nhiều đối tượng tùy ý, bao gồm cả những yêu cầu phân bổ đống để khởi tạo như Vec, HashMapvà những người khác.

Nếu bạn không thể khởi tạo giá trị ngay lập tức, ví dụ: nó phụ thuộc vào đầu vào của người dùng, bạn cũng có thể phải ném Optionvào đó, trong trường hợp đó việc truy cập nó sẽ hơi khó sử dụng:

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

Nhìn vào phần conststaticcủa cuốn sách Rust .

Bạn có thể sử dụng một cái gì đó như sau:

const N: i32 = 5; 

hoặc là

static N: i32 = 5;

trong không gian toàn cầu.

Nhưng chúng không thể thay đổi. Để có thể thay đổi, bạn có thể sử dụng một cái gì đó như:

static mut N: i32 = 5;

Sau đó tham khảo chúng như:

unsafe {
    N += 1;

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

1
Vui lòng giải thích sự khác biệt giữa const Var: Tystatic Var: Ty?
Nawaz

4

Tôi mới sử dụng Rust, nhưng giải pháp này có vẻ hoạt động:

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

Một giải pháp khác là khai báo cặp tx / rx kênh chéo như một biến toàn cục bất biến. Kênh phải được giới hạn và chỉ có thể chứa 1 phần tử. Khi bạn khởi tạo biến toàn cục, hãy đẩy cá thể toàn cục vào kênh. Khi sử dụng biến toàn cục, hãy bật kênh để lấy nó và đẩy nó trở lại khi sử dụng xong.

Cả hai giải pháp phải cung cấp một cách tiếp cận an toàn để sử dụng các biến toàn cục.


10
Không có ích lợi gì &'static Arc<Mutex<...>>vì nó không bao giờ có thể bị phá hủy và không có lý do gì để sao chép nó; bạn chỉ có thể sử dụng &'static Mutex<...>.
trentcl

1

Có thể phân bổ đống cho các biến tĩnh nếu bạn sử dụng macro lazy_static như đã thấy trong tài liệu

Sử dụng macro này, có thể có các tĩnh yêu cầu mã được thực thi trong thời gian chạy để được khởi tạo. Điều này bao gồm bất kỳ thứ gì yêu cầu phân bổ heap, như vectơ hoặc bản đồ băm, cũng như bất kỳ thứ gì yêu cầu tính toán các lệnh gọi hàm.

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

Một câu trả lời hiện có đã nói về lười tĩnh . Vui lòng chỉnh sửa câu trả lời của bạn để chứng minh rõ ràng giá trị mà câu trả lời này mang lại so với những câu trả lời hiện có.
Shepmaster
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.