Programmingシステム/バックエンドおよび組み込み開発者

Rustにおけるグローバル変数(static)の取り扱いルールについて教えてください。同時アクセス時の安全性はどのように確保され、初期化や同期の異なる方法はどのように異なりますか?

Hintsage AIアシスタントで面接を突破

回答

Rustでは、グローバル変数はstaticキーワードを使って宣言されます。このような変数はプログラムの実行中ずっと存在します。デフォルトでは、不変のstaticへのアクセスは安全ですが、可変のものにはunsafeを使う必要があります。コンパイラはスレッド間の競合がないことを保証できないからです。

可変のグローバル変数への安全なアクセスのためには、特別なラッパー型(MutexRwLockAtomic*型や外部クレート(lazy_staticonce_cell))を使用します。"怠惰な"初期化は、最初のアクセス時にオブジェクトの生成を遅らせることを意味します。

例:

static COUNTER: AtomicUsize = AtomicUsize::new(0); static ref CONFIG: Config = read_config(); // lazy_staticやonce_cellを使って

トリックの質問

特殊な型を使用せずに、単一のスレッドからのみ使用される可変のグローバル変数を宣言できますか?unsafeを書く必要がありますか?

回答: コンパイラは、たとえロジックが単一スレッドであっても、グローバル変数の変更には常にunsafeを使用することを要求します。これは全てのstatic mutに対する一般的な要件です。変数が同期構造体(Mutex、アトミックなど)にラップされている場合にのみ、コンパイラは安全なアクセスを許可します。

static mut VALUE: i32 = 0; unsafe { VALUE += 1; }

トピックの微妙な詳細を知らないために発生した実際のエラーの例


事例

Webサービスで、グローバルキャッシュの初期化時に、通常のstatic変数を文字列で使用しました。複数のスレッドが同時に書き込み、メモリの"ビット"やスレッド競合によるアプリケーションのクラッシュが発生しました。


事例

Rustでのコマンドラインユーティリティでは、"mainを介してのみ動作する"ので、同期なしで静的変数を変更しました。後にコードをマルチスレッド用にエスケープしましたが、グローバルミュータブルステートを忘れ、非常に取り扱いの難しいバグを引き起こしました。


事例

組み込みプログラムでは、起動時に関数を通じてstatic CONFIGを不適切に初期化しましたが、それが正確に1回だけ呼ばれることが保証されていませんでした。その結果、一部のコードが未初期化の設定(null参照)にアクセスしました。