Rust에서 전역 변수를 사용할 수 있습니까?


104

일반적으로 전역 변수는 피해야한다는 것을 알고 있습니다. 그럼에도 불구하고 실용적인 의미에서 때때로 변수를 사용하는 것이 바람직하다고 생각합니다 (변수가 프로그램에 필수적인 상황에서).

Rust를 배우기 위해 저는 현재 GitHub에서 sqlite3와 Rust / sqlite3 패키지를 사용하는 데이터베이스 테스트 프로그램을 작성하고 있습니다. 결과적으로 (내 테스트 프로그램에서) (전역 변수의 대안으로) 약 12 ​​개의 함수 사이에 데이터베이스 변수를 전달해야합니다. 예는 다음과 같습니다.

  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
A에 대한 안전 솔루션을 참조하십시오 I 글로벌, 가변 싱글을 만들려면 어떻게해야합니까? .
Shepmaster

OP가 경험하는 오류 ConnectionOption<Connection>유형 내부 에 저장 하려고 시도 Option<Connection>하고 Connection. 이러한 오류가 해결되고 (사용하여 Some()) unsafe블록 을 사용하면 원래 시도한대로 코드가 작동합니다 (스레드에 안전하지 않은 방식 임에도 불구하고).
TheHansinator

답변:


65

가능하지만 힙 할당은 직접 허용되지 않습니다. 힙 할당은 런타임에 수행됩니다. 다음은 몇 가지 예입니다.

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

44

스레드 로컬 인 경우 정적 변수를 상당히 쉽게 사용할 수 있습니다.

단점은 프로그램이 생성 할 수있는 다른 스레드에 개체가 표시되지 않는다는 것입니다. 장점은 진정한 글로벌 상태와 달리 완전히 안전하고 사용하기 불편하지 않다는 것입니다. 진정한 글로벌 상태는 모든 언어에서 엄청난 고통입니다. 예를 들면 다음과 같습니다.

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

22

Rust 책constand 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

4

저는 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()));
}

또 다른 해결책은 크로스 빔 채널 tx / rx 쌍을 변경 불가능한 전역 변수로 선언하는 것입니다. 채널은 경계가 있어야하며 1 개의 요소 만 보유 할 수 있습니다. 전역 변수를 초기화 할 때 전역 인스턴스를 채널로 푸시합니다. 전역 변수를 사용하는 경우 채널을 팝하여 획득하고 사용이 끝나면 뒤로 밀어냅니다.

두 솔루션 모두 전역 변수 사용에 대한 안전한 접근 방식을 제공해야합니다.


10
에 아무 소용이 없다 &'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.