เป็นไปได้ไหมที่จะใช้ตัวแปรส่วนกลางใน Rust


114

ฉันรู้ว่าโดยทั่วไปต้องหลีกเลี่ยงตัวแปรส่วนกลาง อย่างไรก็ตามฉันคิดว่าในทางปฏิบัติบางครั้งก็เป็นที่พึงปรารถนา (ในสถานการณ์ที่ตัวแปรเป็นส่วนสำคัญของโปรแกรม) ที่จะใช้พวกเขา

เพื่อเรียนรู้ 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) {
   |              ^^^^

4
สำหรับวิธีการแก้ปัญหาที่ปลอดภัยโปรดดูที่ฉันจะสร้างโกลบอลซิงเกิลที่เปลี่ยนแปลงได้อย่างไร .
Shepmaster

ฉันควรทราบที่นี่ว่าข้อผิดพลาดที่ OP ประสบนั้นเกี่ยวข้องกับการพยายามจัดเก็บประเภทConnectionภายในOption<Connection>และพยายามใช้Option<Connection>เป็นไฟล์Connection. หากข้อผิดพลาดเหล่านั้นได้รับการแก้ไข (โดยใช้Some()) และพวกเขาใช้unsafeบล็อกตามที่เคยพยายามโค้ดของพวกเขาจะใช้งานได้ (แม้ว่าจะเป็นวิธีที่ไม่ปลอดภัยของเธรดก็ตาม)
TheHansinator

คำตอบ:


76

เป็นไปได้ แต่ไม่อนุญาตให้จัดสรรฮีปโดยตรง การจัดสรรฮีปจะดำเนินการที่รันไทม์ นี่คือตัวอย่างบางส่วน:

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ตัวเลือกหมายความว่าทุกส่วนของรหัสที่ใช้การเชื่อมต่อจะต้องถูกทำเครื่องหมายว่าไม่ปลอดภัย
Kamek

1
@Kamek การเข้าถึงครั้งแรกจะต้องไม่ปลอดภัย โดยปกติฉันจะใช้กระดาษห่อแบบบางของมาโครเพื่อปกปิดสิ่งนั้น
jhpratt

48

คุณสามารถใช้ตัวแปรคงที่ค่อนข้างง่ายตราบเท่าที่เป็นเธรดโลคัล

ข้อเสียคือวัตถุจะมองไม่เห็นในเธรดอื่นที่โปรแกรมของคุณอาจวางไข่ ข้อดีก็คือไม่เหมือนรัฐระดับโลกอย่างแท้จริงมันปลอดภัยและไม่เจ็บปวดที่จะใช้ - สภาวะโลกที่แท้จริงเป็นความเจ็บปวดอย่างมากในทุกภาษา นี่คือตัวอย่าง:

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

23

ดูที่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

6

ฉันยังใหม่กับ 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()));
}

อีกวิธีหนึ่งคือการประกาศคู่ crossbeam channel tx / rx เป็นตัวแปรโกลบอลที่ไม่เปลี่ยนรูป ช่องควรมีขอบเขตและสามารถเก็บได้ 1 องค์ประกอบเท่านั้น เมื่อคุณเริ่มต้นตัวแปรส่วนกลางให้พุชอินสแตนซ์ส่วนกลางลงในแชนเนล เมื่อใช้ตัวแปร global ให้เปิดช่องเพื่อรับและดันกลับเมื่อใช้งานเสร็จ

โซลูชันทั้งสองควรเป็นแนวทางที่ปลอดภัยในการใช้ตัวแปรส่วนกลาง


11
มีจุดที่จะไม่ได้&'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");
}

คำตอบมีอยู่แล้วพูดเกี่ยวกับการคงขี้เกียจ โปรดแก้ไขคำตอบของคุณเพื่อแสดงให้เห็นอย่างชัดเจนว่าคำตอบนี้นำมาซึ่งคุณค่าใดเมื่อเทียบกับคำตอบที่มีอยู่
Shepmaster
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.