ПрограммированиеSystem/backend и Embedded-разработчик

Расскажите о правилах работы с глобальными переменными (static) в Rust. Как обеспечивается безопасность при одновременном доступе и чем отличаются разные способы инициализации и синхронизации?

Проходите собеседования с ИИ помощником Hintsage

Ответ

В 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).