Глобальные переменные в Rust: мифы, реальность и безопасные альтернативы

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: "А где тут глобальные переменные?" — первый шок новичка в Rust

Вы только начали изучать Rust, написали пару программ, и вдруг понимаете: вам нужно, чтобы какая-то переменная была доступна из любой точки кода. В Python или JavaScript вы бы просто написали global_var = 42 на верхнем уровне — и готово. Но в Rust... компилятор смотрит на вас с укоризной и выдает ошибку.

Вопрос "Is it possible to use global variables in Rust?" — один из самых популярных на Stack Overflow. И не зря. Rust, в отличие от многих языков, не позволяет просто так взять и объявить глобальную переменную. Почему? Потому что безопасность памяти — это не просто модное слово, а фундамент языка.

В этой статье мы разберем все способы работы с глобальными данными в Rust: от простых констант до потокобезопасных хранилищ. Вы узнаете, как обойти ограничения компилятора, не жертвуя безопасностью, и когда глобальные переменные — это зло, а когда — необходимость.

Почему Rust запрещает "обычные" глобальные переменные?

Прежде чем искать решения, давайте поймем проблему. В Rust действуют строгие правила владения (ownership) и заимствования (borrowing). Глобальная переменная, которая может быть изменена из любого потока в любой момент — это прямой путь к гонке данных (data race).

Представьте: два потока одновременно пытаются записать разные значения в одну и ту же глобальную переменную. Результат непредсказуем. В C++ или C это приводит к неопределенному поведению. Rust же гарантирует, что такое просто не скомпилируется.

Поэтому компилятор требует от нас явно указать:

  • Переменная неизменяема? Используйте const или static.
  • Переменная изменяема и используется в одном потоке? Используйте static mut (с unsafe).
  • Переменная изменяема и используется в нескольких потоках? Используйте Mutex, RwLock или атомарные типы.

Способ 1: Константы (const) — самый безопасный вариант

Если ваше "глобальное значение" на самом деле является константой, которая никогда не меняется, используйте const:

const MAX_CONNECTIONS: u32 = 100;
const DATABASE_URL: &str = "postgresql://localhost:5432/mydb";

fn main() {
    println!("Максимум подключений: {}", MAX_CONNECTIONS);
}

Константы в Rust — это не просто переменные. Они инлайнятся компилятором в место использования. То есть, во время компиляции MAX_CONNECTIONS будет заменена на число 100. Это эффективно, но означает, что у константы нет фиксированного адреса в памяти. Если вам нужно именно место в памяти (например, для FFI), используйте static.

Способ 2: Статические переменные (static)

Если вам нужна глобальная переменная с фиксированным адресом в памяти, используйте static:

static APP_NAME: &str = "My Rust Application";
static VERSION: u32 = 1;

fn main() {
    println!("Запуск {} версии {}", APP_NAME, VERSION);
}

По умолчанию static переменные неизменяемы. Это гарантирует безопасность в многопоточной среде. Но что, если нужно изменять глобальное состояние?

Способ 3: Изменяемые статические переменные (static mut) — осторожно, unsafe!

Rust позволяет объявить изменяемую статическую переменную с помощью static mut. Но есть подвох: любое чтение или запись такой переменной требует блока unsafe.

static mut COUNTER: u32 = 0;

fn increment() {
    unsafe {
        COUNTER += 1;
    }
}

fn main() {
    increment();
    unsafe {
        println!("Счетчик: {}", COUNTER);
    }
}

Важно: static mut — это не потокобезопасно. Если вы попытаетесь вызвать increment() из двух потоков одновременно, получите гонку данных. Компилятор Rust вам это позволит (потому что unsafe), но программа будет работать некорректно. Поэтому static mut стоит использовать только в однопоточных программах или в очень специфичных случаях (например, при работе с FFI).

Способ 4: Mutex — потокобезопасное глобальное состояние

Самый распространенный способ работы с изменяемыми глобальными данными в Rust — это Mutex (взаимное исключение). Mutex гарантирует, что только один поток может получить доступ к данным в любой момент времени.

use std::sync::Mutex;

static COUNTER: Mutex<u32> = Mutex::new(0);

fn main() {
    let mut handles = vec![];
    
    for _ in 0..10 {
        let handle = std::thread::spawn(|| {
            let mut counter = COUNTER.lock().unwrap();
            *counter += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Финальное значение: {}", *COUNTER.lock().unwrap());
}

Обратите внимание: Mutex::new(0) работает, потому что Mutex является константной функцией (const fn), что позволяет инициализировать статическую переменную во время компиляции.

Для ситуаций, где чтение происходит гораздо чаще, чем запись, используйте RwLock — он позволяет одновременно читать данным нескольким потокам.

Способ 5: Ленивая инициализация с OnceCell и OnceLock

Что делать, если глобальное значение нельзя вычислить во время компиляции? Например, конфигурация, читаемая из файла, или соединение с базой данных. Для этого в Rust есть OnceCell (из крейта once_cell) или std::sync::OnceLock (стабилизирован в Rust 1.70).

use std::sync::OnceLock;

static CONFIG: OnceLock<String> = OnceLock::new();

fn get_config() -> &'static str {
    CONFIG.get_or_init(|| {
        // Здесь может быть чтение из файла или сложные вычисления
        std::fs::read_to_string("config.toml").expect("Не удалось прочитать конфиг")
    })
}

fn main() {
    println!("Конфиг: {}", get_config());
}

OnceLock гарантирует, что инициализация произойдет только один раз, даже если несколько потоков одновременно вызовут get_or_init. Это идеальный паттерн для глобальных синглтонов.

Способ 6: Атомарные типы для простых чисел

Если ваша глобальная переменная — это просто число (u32, i64, bool и т.д.), и вам нужно только атомарно увеличивать/уменьшать его, используйте атомарные типы:

<                

Блоги

Рекомендаций по книгам