In Rust werden globale Variablen mit dem Schlüsselwort static deklariert. Solche Variablen leben während der gesamten Programmausführung. Per Standard ist der Zugriff auf unveränderliche static sicher, aber für veränderliche müssen unsafe-Blöcke verwendet werden, da der Compiler nicht garantieren kann, dass es keine Thread-Race-Conditions gibt.
Für den sicheren Zugriff auf veränderliche globale Variablen werden spezielle Wrapper-Typen verwendet: Mutex, RwLock, Atomic*-Typen oder externe Crates (lazy_static, once_cell). Die Initialisierung erfolgt "lazy", wenn es erforderlich ist, die Erstellung des Objekts beim ersten Zugriff zu verzögern.
Beispiele:
static COUNTER: AtomicUsize = AtomicUsize::new(0); static ref CONFIG: Config = read_config(); // mit lazy_static oder once_cell
Kann man eine veränderliche globale Variable ohne spezielle Typen deklarieren, wenn sie nur aus einem Thread verwendet wird? Muss man unsafe schreiben?
Antwort:
Der Compiler verlangt die Verwendung von unsafe bei jeder Änderung einer globalen Variablen, selbst wenn die Logik einsträngig ist. Dies ist eine allgemeine Anforderung für alle static mut. Nur wenn die Variable in eine synchronisierende Struktur (Mutex, Atomar usw.) eingekapselt ist, erlaubt der Compiler einen sicheren Zugriff.
static mut VALUE: i32 = 0; unsafe { VALUE += 1; }
Geschichte
In einem Web-Service wurde bei der Initialisierung des globalen Caches eine gewöhnliche static Variabel mit einer Zeichenkette verwendet. Mehrere Threads schrieben gleichzeitig darauf, was zu "verfaulten" Speicherstellen und Abstürzen der Anwendung aufgrund von Thread-Rennen führte.
Geschichte
In einem Kommandozeilenwerkzeug in Rust wurde eine statische Variable ohne Synchronisation geändert, weil sie "nur über Main funktioniert". Später wurde der Code für die Multithreadfähigkeit eingeschlossen, wobei die globale veränderbare Zustände vergessen wurden, was zu sehr schwierigen Bugs führte.
Geschichte
In einem Embedded-Programm wurde die static CONFIG beim Start über eine Funktion falsch initialisiert, garantierte jedoch nicht, dass sie genau einmal aufgerufen wird. Infolgedessen greifende Teile des Codes auf eine nicht initialisierte Konfiguration zu (null reference).