В Rust глобальные переменные объявляют с помощью ключевого слова static. Такие переменные живут всё время выполнения программы. По умолчанию доступ к неизменяемым static безопасен, но к изменяемым требуется использовать unsafe, поскольку компилятор не может гарантировать отсутствие гонок потоков.
Для безопасного доступа к изменяемым глобальным переменным применяют специальные типы-обёртки: Mutex, RwLock, Atomic* типы или внешние crate (lazy_static, once_cell). Инициализация "ленивая", если требуется отложенное создание объекта при первом обращении.
Примеры:
static COUNTER: AtomicUsize = AtomicUsize::new(0); static ref CONFIG: Config = read_config(); // c помощью lazy_static или once_cell
Можно ли объявить изменяемую глобальную переменную без специальных типов, если она будет использоваться только из одного потока? Нужно ли писать unsafe?
Ответ:
Компилятор потребует использовать unsafe при любом изменении глобальной переменной, даже если логика однопоточная. Это общее требование для всех static mut. Только если переменная обёрнута в синхронизирующую структуру (Mutex, атомик, etc.), компилятор разрешит безопасный доступ.
static mut VALUE: i32 = 0; unsafe { VALUE += 1; }
История
В web-сервисе при инициализации глобального кэша использовали обычную static переменную со строкой. В него одновременно писали несколько потоков, возникали "биты" памяти и падения приложения из-за гонки потоков.
История
В командном утилите на Rust изменяли статическую переменную без синхронизации, потому что "работает только через main". Позже код экранировали для многопоточности, забыв о global mutable state, что вызвало очень трудноловимые баги.
История
В embedded-программе неправильно инициализировали static CONFIG через функцию при старте, но не гарантировали, что она будет вызвана ровно один раз. В результате некоторые части кода обращались к неинициализированной конфигурации (null reference).