Можно ли использовать глобальные переменные в Rust?
Проблема глобальных переменных в Rust
Rust жёстко контролирует безопасность памяти. Глобальные изменяемые переменные (как в C++) — прямой путь к гонкам данных (data races). Поэтому компилятор запрещает прямую мутацию. Вместо этого Rust предлагает безопасные обёртки.
Решение 1: Mutex для потокобезопасности
Самый простой способ — использовать Mutex (или RwLock). Он гарантирует, что только один поток может изменять значение в любой момент времени.
use std::sync::Mutex;
static COUNTER: Mutex<i32> = Mutex::new(0);
fn increment() {
let mut num = COUNTER.lock().unwrap();
*num += 1;
}
fn get_value() -> i32 {
*COUNTER.lock().unwrap()
}
fn main() {
increment();
increment();
println!("Счётчик: {}", get_value()); // 2
}
Как это работает:
Mutex::new(0)создаёт глобальный мьютекс с начальным значением.lock()захватывает блокировку. Если другой поток уже держит блокировку, текущий поток ждёт.unwrap()— если мьютекс отравлен (poisoned) из-за паники в другом потоке, программа упадёт. Для продакшена стоит обрабатывать ошибку.- После выхода из области видимости блокировка автоматически освобождается.
Решение 2: lazy_static или once_cell для ленивой инициализации
Если глобальная переменная требует сложной инициализации (например, чтение файла конфигурации), используйте крейт lazy_static (или once_cell, который входит в стандартную библиотеку с Rust 1.70).
// Cargo.toml: lazy_static = "1.4"
use lazy_static::lazy_static;
use std::sync::Mutex;
lazy_static! {
static ref CONFIG: Mutex<Vec<String>> = Mutex::new(vec![
"option1".to_string(),
"option2".to_string(),
]);
}
fn main() {
let mut config = CONFIG.lock().unwrap();
config.push("option3".to_string());
println!("{:?}", *config);
}
Альтернатива без внешних крейтов (Rust 1.70+):
use std::sync::LazyLock;
use std::sync::Mutex;
static CONFIG: LazyLock<Mutex<Vec<String>>> = LazyLock::new(|| {
Mutex::new(vec!["default".to_string()])
});
Решение 3: Atomic для простых чисел
Если вам нужно хранить целочисленный счётчик или флаг, используйте атомарные типы (AtomicI32, AtomicBool и т.д.). Они работают без блокировок и очень эффективны.
use std::sync::atomic::{AtomicI32, Ordering};
static COUNTER: AtomicI32 = AtomicI32::new(0);
fn increment() {
COUNTER.fetch_add(1, Ordering::SeqCst);
}
fn get_value() -> i32 {
COUNTER.load(Ordering::SeqCst)
}
fn main() {
increment();
increment();
println!("Счётчик: {}", get_value()); // 2
}
Важно: Ordering определяет гарантии видимости. SeqCst — самый строгий (и безопасный для новичков). Для продакшена можно использовать Relaxed или Acquire/Release, но это требует понимания модели памяти.
Когда не стоит использовать глобальные переменные
В Rust принято передавать состояние через параметры функций или через структуры. Это делает код тестируемым и предсказуемым. Глобальные переменные — это исключение для редких случаев:
- Глобальный логгер или конфигурация, которая не меняется после старта.
- Счётчики метрик (например, число обработанных запросов).
- Кэш с однократной инициализацией.
Если вы пишете библиотеку, избегайте глобального состояния — оно мешает пользователям контролировать поведение.
<<Альтернатива: передача состояния через параметры
В Rust принято избегать глобальных переменных. Вместо этого используйте структуру, которая хранит состояние, и передавайте её как параметр. Это делает код гибким и тестируемым.
struct AppState {
counter: i32,
}
impl AppState {
fn new() -> Self {
AppState { counter: 0 }
}
fn increment(&mut self) {
self.counter += 1;
}
fn get_value(&self) -> i32 {
self.counter
}
}
fn main() {
let mut state = AppState::new();
state.increment();
state.increment();
println!("Счётчик: {}", state.get_value()); // 2
}
Плюсы:
- Нет проблем с потоками — каждый поток может иметь свою копию.
- Легко тестировать — можно создать несколько экземпляров.
- Явная передача зависимостей (dependency injection).
- Не нужно использовать
unsafeили блокировки.
Когда это не подходит: если состояние действительно должно быть глобальным (например, реестр обработчиков в фреймворке). В таких случаях используйте Mutex или Atomic из первого ответа.
Совет: начните с локального состояния. Если почувствуете, что передача через параметры становится слишком громоздкой (например, в 10+ вложенных функциях), задумайтесь о внедрении зависимостей через структуры или трейты, а не о глобальных переменных.