Heim >Technologie-Peripheriegeräte >IT Industrie >So verwenden Sie idiomatisch globale Variablen in Rost
globale Variablen in Rost deklarieren und verwenden, kann schwierig sein. In der Regel sorgt Rust für diese Sprache Robustheit, indem wir uns dazu zwingen, sehr explizit zu sein.
In diesem Artikel werde ich die Fallstricke diskutieren, vor dem der Rost -Compiler uns retten möchte. Dann zeige ich Ihnen die besten Lösungen für verschiedene Szenarien.
Es gibt viele Optionen für die Implementierung des globalen Staates in Rost. Wenn Sie es eilig haben, finden Sie hier einen kurzen Überblick über meine Empfehlungen.
Sie können über die folgenden Links zu bestimmten Abschnitten dieses Artikels springen:
Beginnen wir mit einem Beispiel dafür, wie keine globalen Variablen verwendet werden können. Angenommen, ich möchte die Startzeit des Programms in einer globalen Zeichenfolge speichern. Später möchte ich von mehreren Threads auf den Wert zugreifen.
Ein Rostanfänger könnte versucht sein, eine globale Variable genau wie jede andere Variable in Rost zu deklarieren. Das vollständige Programm könnte dann so aussehen:
<span>use chrono<span>::</span>Utc; </span> <span>let START_TIME = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>let thread_1 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 1 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>let thread_2 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 2 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>// Join threads and panic on error to show what went wrong </span> thread_1<span>.join().unwrap(); </span> thread_2<span>.join().unwrap(); </span><span>} </span>
Probieren Sie es selbst auf dem Spielplatz aus!
Dies ist eine ungültige Syntax für Rost. Das LET -Keyword kann im globalen Bereich nicht verwendet werden. Wir können nur statisch oder const verwenden. Letzteres erklärt eine echte Konstante, keine Variable. Nur Static gibt uns eine globale Variable.
Die Argumentation dahinter ist, dass es zur Laufzeit eine Variable auf dem Stapel zuteilt. Beachten Sie, dass dies bei der Zuordnung auf dem Haufen wie in T = Box :: New (); Im generierten Maschinencode gibt es immer noch einen Zeiger in den Haufen, der auf dem Stapel gespeichert wird.
globale Variablen werden im Datensegment des Programms gespeichert. Sie haben eine feste Adresse, die sich während der Ausführung nicht ändert. Daher kann das Codesegment konstante Adressen enthalten und benötigt überhaupt keinen Platz auf dem Stapel.
Okay, wir können verstehen, warum wir eine andere Syntax brauchen. Rust will als moderne Systemprogrammiersprache sehr explizit über das Gedächtnismanagement sein.
Versuchen wir es erneut mit statisch:
<span>use chrono<span>::</span>Utc; </span> <span>let START_TIME = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>let thread_1 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 1 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>let thread_2 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 2 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>// Join threads and panic on error to show what went wrong </span> thread_1<span>.join().unwrap(); </span> thread_2<span>.join().unwrap(); </span><span>} </span>
Der Compiler ist noch nicht glücklich:
<span>use chrono<span>::</span>Utc; </span> <span>static START_TIME: String = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>// ... </span><span>} </span>
hm, sodass der Initialisierungswert einer statischen Variablen nicht zur Laufzeit berechnet werden kann. Dann lassen Sie es vielleicht einfach nicht initialisiert?
error<span>[E0015]: calls in statics are limited to constant functions, tuple structs and tuple variants </span> <span>--> src/main.rs:3:24 </span> <span>| </span><span>3 | static start: String = Utc::now().to_string(); </span> <span>| ^^^^^^^^^^^^^^^^^^^^^^ </span>
Dies ergibt einen neuen Fehler:
<span>use chrono<span>::</span>Utc; </span> <span>static START_TIME; </span> <span>pub fn main() { </span> <span>// ... </span><span>} </span>
Das funktioniert auch nicht! Alle statischen Werte müssen vollständig initialisiert und gültig sein, bevor ein Benutzercode ausgeführt wird.
Wenn Sie aus einer anderen Sprache wie JavaScript oder Python zu Rost kommen, scheint dies unnötig restriktiv zu sein. Jeder C -Guru kann Ihnen Geschichten über das Fiasko der statischen Initialisierungsreihenfolge erzählen, die zu einer undefinierten Initialisierungsreihenfolge führen können, wenn wir nicht vorsichtig sind.
Stellen Sie sich zum Beispiel so etwas vor:
<span>Compiling playground v0.0.1 (/playground) </span>error<span>: free static item without body </span> <span>--> src/main.rs:21:1 </span> <span>| </span><span>3 | static START_TIME; </span> <span>| ^^^^^^^^^^^^^^^^^- </span> <span>| | </span> <span>| help: provide a definition for the static: `= <expr>;` </span>
In diesem Code -Snippet gibt es aufgrund kreisförmiger Abhängigkeiten keine sichere Initialisierungsreihenfolge.
Wenn es C wäre, was sich nicht für die Sicherheit interessiert, wäre das Ergebnis A: 1 B: 1 C: 2. Es ist null initialisiert, bevor ein Code ausgeführt wird, und dann wird die Reihenfolge von oben nach unten definiert Innerhalb jeder Kompilierungseinheit.
Zumindest ist es definiert, was das Ergebnis ist. Das „Fiasko“ beginnt jedoch, wenn sich die statischen Variablen aus verschiedenen .CPP -Dateien und damit unterschiedlichen Kompilierungseinheiten stammen. Dann ist die Reihenfolge undefiniert und hängt normalerweise von der Reihenfolge der Dateien in der Befehlszeile der Kompilierung ab.
In Rost ist keine initialisierende Nullinitialisierung eine Sache. Schließlich ist Null ein ungültiger Wert für viele Typen, wie z. B. Box. Darüber hinaus akzeptieren wir bei Rost keine seltsamen Bestellprobleme. Solange wir uns von Unsicherheit fernhalten, sollte der Compiler uns nur erlauben, vernünftige Code zu schreiben. Und deshalb hindert der Compiler uns daran, eine einfache Laufzeitinitialisierung zu verwenden.
Aber kann ich die Initialisierung mit keinem umgehen, das Äquivalent eines Null-Zeigers? Zumindest entspricht dies alles mit dem Rostypsystem. Sicherlich kann ich die Initialisierung einfach an die Spitze der Hauptfunktion verschieben, oder?
<span>use chrono<span>::</span>Utc; </span> <span>let START_TIME = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>let thread_1 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 1 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>let thread_2 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 2 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>// Join threads and panic on error to show what went wrong </span> thread_1<span>.join().unwrap(); </span> thread_2<span>.join().unwrap(); </span><span>} </span>
ah, nun, der Fehler, den wir bekommen, ist…
<span>use chrono<span>::</span>Utc; </span> <span>static START_TIME: String = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>// ... </span><span>} </span>
Zu diesem Zeitpunkt könnte ich es in einen unsicheren {...} -Block einwickeln und es würde funktionieren. Manchmal ist dies eine gültige Strategie. Vielleicht zu testen, ob der Rest des Codes wie erwartet funktioniert. Aber es ist nicht die idiomatische Lösung, die ich Ihnen zeigen möchte. Lassen Sie uns also Lösungen untersuchen, die vom Compiler garantiert sicher sind.
Sie haben möglicherweise bereits festgestellt, dass für dieses Beispiel globale Variablen überhaupt nicht erforderlich sind. Und meistens sollten wir sie vermeiden, wenn wir uns eine Lösung ohne globale Variablen vorstellen können.
Die Idee hier ist, die Deklaration in die Hauptfunktion zu setzen:
error<span>[E0015]: calls in statics are limited to constant functions, tuple structs and tuple variants </span> <span>--> src/main.rs:3:24 </span> <span>| </span><span>3 | static start: String = Utc::now().to_string(); </span> <span>| ^^^^^^^^^^^^^^^^^^^^^^ </span>
Das einzige Problem ist der Ausleihen-Prüfer:
<span>use chrono<span>::</span>Utc; </span> <span>static START_TIME; </span> <span>pub fn main() { </span> <span>// ... </span><span>} </span>
Dieser Fehler ist nicht gerade offensichtlich. Der Compiler teilt uns mit, dass der geschafte Thread länger als der Wert start_time leben kann, der im Stapelrahmen der Hauptfunktion lebt.
technisch gesehen können wir sehen, dass dies unmöglich ist. Die Threads sind verbunden, daher wird der Hauptfaden nicht beendet, bevor die Kinderfäden beendet sind.
Aber der Compiler ist nicht klug genug, um diesen speziellen Fall herauszufinden. Wenn ein neuer Thread hervorgebracht wird, kann die vorgesehene Schließung im Allgemeinen nur Gegenstände mit einer statischen Lebensdauer ausleihen. Mit anderen Worten, die geliehenen Werte müssen für die vollständige Programmlebensdauer am Leben sein.
Für alle, die gerade etwas über Rost lernen, könnte dies der Punkt sein, an dem Sie sich an globale Variablen wenden möchten. Es gibt jedoch mindestens zwei Lösungen, die viel einfacher sind. Am einfachsten ist es, den Stringwert zu klonen und dann das Eigentum der Zeichenfolgen in die Schließungen zu verschieben. Das erfordert natürlich eine zusätzliche Zuordnung und etwas zusätzliches Speicher. Aber in diesem Fall ist es nur eine kurze Zeichenfolge und nichts leistungskritisch.
Aber was wäre, wenn es ein viel größeres Objekt wäre, um sie zu teilen? Wenn Sie es nicht klonen möchten, wickeln Sie es hinter einen referenzbezogenen intelligenten Zeiger ein. RC ist der einsthread-Referenztyp. ARC ist die atomare Version, die die Werte zwischen Threads sicher teilen kann.
Um den Compiler zufrieden zu stellen, können wir ARC wie folgt verwenden:
<span>use chrono<span>::</span>Utc; </span> <span>let START_TIME = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>let thread_1 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 1 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>let thread_2 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 2 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>// Join threads and panic on error to show what went wrong </span> thread_1<span>.join().unwrap(); </span> thread_2<span>.join().unwrap(); </span><span>} </span>
Probieren Sie es selbst auf dem Spielplatz aus!
Dies war ein kurzer Überblick darüber, wie der Status zwischen Threads geteilt wird und gleichzeitig globale Variablen vermeidet. Über das, was ich Ihnen bisher gezeigt habe, benötigen Sie möglicherweise auch innenverstärzbarkeit, um den gemeinsam genutzten Zustand zu ändern. Die vollständige Abdeckung der inneren Veränderlichkeit liegt außerhalb des Rahmens dieses Artikels. Aber in diesem speziellen Beispiel würde ich Arc
meiner Erfahrung nach sind die häufigsten Anwendungsfälle für den globalen Zustand keine Variablen, sondern Konstanten. In Rost kommen sie in zwei Geschmacksrichtungen:
beide können mit Kompilierungszeitkonstanten initialisiert werden. Dies können einfache Werte sein, wie 42 oder "Hello World". Oder es könnte ein Ausdruck sein, an dem mehrere andere Kompilierungszeitkonstanten und -Funktionen als const gekennzeichnet sind. Solange wir kreisförmige Abhängigkeiten vermeiden. (Weitere Details zu ständigen Ausdrücken in der Rostreferenz finden.)
<span>use chrono<span>::</span>Utc; </span> <span>static START_TIME: String = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>// ... </span><span>} </span>
Normalerweise ist const die bessere Wahl - es sei denn, Sie benötigen eine innenverstärkte Veränderbarkeit, oder Sie möchten ausdrücklich das Einbinden vermeiden.
Sollten Sie innenverstärkbarkeit benötigt werden, gibt es mehrere Optionen. Für die meisten Primitiven gibt es eine entsprechende Atomvariante in std :: sync :: atomic. Sie bieten eine saubere API zum Laden, Speichern und Aktualisieren von Werten atomisch.
In Abwesenheit von Atomik ist die übliche Wahl ein Schloss. Die Standardbibliothek von Rust bietet ein Leseschloss (RWLOCK) und ein gegenseitiges Ausschlussschloss (Mutex).
Wenn Sie jedoch den Wert zur Laufzeit berechnen oder eine Heap-Adlocation benötigen, dann sind const und static keine Hilfe.
Die meisten Anwendungen, die ich schreibe, haben nur einen einzelnen Thread. In diesem Fall ist ein Sperrmechanismus nicht erforderlich.
Wir sollten jedoch keine statische MUT direkt verwenden und Zugriffe in unsicherem Wickeln wickeln, nur weil es nur einen Thread gibt. Auf diese Weise könnten wir eine ernsthafte Korruption von Speicher haben.
Zum Beispiel könnte uns das Ausleihen von unsicher aus der globalen Variablen gleichzeitig mehrere veränderliche Referenzen geben. Dann konnten wir einen von ihnen verwenden, um über einen Vektor und einen anderen zu iterieren, um Werte aus demselben Vektor zu entfernen. Der Iterator könnte dann über die gültige Speichergrenze hinausgehen, ein potenzieller Absturz, den sicherer Rost verhindert hätte.
Aber die Standardbibliothek kann in einem einzigen Thread "weltweit" -Spale -Werte für einen sicheren Zugriff aufbewahren. Ich spreche von Thread -Einheimischen. In Gegenwart vieler Threads erhält jeder Thread eine unabhängige Kopie der Variablen. Aber in unserem Fall gibt es mit einem einzigen Thread nur eine Kopie.
Thread -Einheimische werden mit dem Thread_local erstellt! Makro. Der Zugriff auf sie erfordert die Verwendung eines Verschlusses, wie im folgenden Beispiel gezeigt:
<span>use chrono<span>::</span>Utc; </span> <span>let START_TIME = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>let thread_1 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 1 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>let thread_2 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 2 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>// Join threads and panic on error to show what went wrong </span> thread_1<span>.join().unwrap(); </span> thread_2<span>.join().unwrap(); </span><span>} </span>
Es ist nicht die einfachste aller Lösungen. Es ermöglicht uns jedoch, einen beliebigen Initialisierungscode durchzuführen, der genau rechtzeitig ausgeführt wird, wenn der erste Zugriff auf den Wert auftritt.
Thread-Locals sind wirklich gut, wenn es um innere Veränderlichkeit geht. Im Gegensatz zu allen anderen Lösungen erfordert es keine Synchronisierung. Dies ermöglicht die Verwendung von Refcell für die Innenverstärkbarkeit, wodurch der Verriegelungsaufwand von Mutex vermieden wird.
Die absolute Leistung von Thread-Locals hängt stark von der Plattform ab. Aber ich habe einige schnelle Tests auf meinem eigenen PC durchgeführt, um es mit der Innenvermutbarkeit zu vergleicht, die sich auf Schlösser stützt, und fand, dass es 10 -fach schneller ist. Ich gehe nicht davon aus
Hier ist ein Beispiel dafürProbieren Sie es selbst auf dem Spielplatz aus!
<span>use chrono<span>::</span>Utc; </span> <span>static START_TIME: String = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>// ... </span><span>} </span>
als Randnotiz, obwohl Threads in der WebAssembly sich von Threads einer x86_64 -Plattform unterscheiden, dieses Muster mit Thread_local! Refcell gilt auch beim Kompilieren von Rost, um im Browser zu laufen. In diesem Fall wäre es übertrieben, einen Ansatz zu verwenden, der für Multi-Thread-Code sicher ist. (Wenn die Idee, Rost im Browser zu laufen
Ein Vorbehalt über Thread-Locals ist, dass ihre Implementierung von der Plattform abhängt. Normalerweise ist dies nichts, was Sie bemerken, aber seien Sie sich bewusst, dass die Drop-Semantik aus diesem Grund plattformabhängig ist.Alles, was gesagt wurde, die Lösungen für Multi-Thread-Globale funktionieren offensichtlich auch für die Einzelfälle. Und ohne innere Veränderlichkeit scheinen diese genauso schnell zu sein wie Faden-Lokale.
Also schauen wir uns das als nächstes an.
globale Variablen mit Multi-Thread-Variablen mit der Laufzeitinitialisierung
Das Beispiel in der offiziellen Dokumentation ist ein guter Ausgangspunkt. Sollten Sie auch Innenverständer benötigen, müssen Sie diesen Ansatz mit einem Leseschloss oder einem Mutex kombinieren. So könnte das aussehen:
<span>use chrono<span>::</span>Utc; </span> <span>let START_TIME = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>let thread_1 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 1 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>let thread_2 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 2 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>// Join threads and panic on error to show what went wrong </span> thread_1<span>.join().unwrap(); </span> thread_2<span>.join().unwrap(); </span><span>} </span>Probieren Sie es selbst auf dem Spielplatz aus!
Wenn Sie nach etwas Einfacherem suchen, kann ich eines von zwei Kisten sehr empfehlen, die ich im nächsten Abschnitt besprechen werde.
Basierend auf Popularität und persönlichem Geschmack möchte ich zwei Bibliotheken empfehlen, die meiner Meinung nach die beste Wahl für einfache globale Variablen in Rost sind.
Sobald die Zelle derzeit für die Standardbibliothek berücksichtigt wird. (Siehe dieses Tracking -Problem.) Wenn Sie sich in einem nächtlichen Compiler befinden, können Sie die instabile API bereits verwenden, indem Sie #! [Feature (Once_cell)] zu den main.rs.
Ihres Projekts hinzufügen.Hier ist ein Beispiel, das einmal_cell in einem stabilen Compiler mit der zusätzlichen Abhängigkeit verwendet wird:
<span>use chrono<span>::</span>Utc; </span> <span>let START_TIME = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>let thread_1 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 1 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>let thread_2 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 2 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>// Join threads and panic on error to show what went wrong </span> thread_1<span>.join().unwrap(); </span> thread_2<span>.join().unwrap(); </span><span>} </span>
Probieren Sie es selbst auf dem Spielplatz aus!
Schließlich gibt es auch faule statische, derzeit die beliebteste Kiste für die Initialisierung globaler Variablen. Es verwendet ein Makro mit einer kleinen Syntaxverlängerung (statische Ref), um globale Variablen zu definieren.
Hier ist das gleiche Beispiel erneut, übersetzt von Ones_cell zu Lazy_static:
<span>use chrono<span>::</span>Utc; </span> <span>let START_TIME = Utc::now().to_string(); </span> <span>pub fn main() { </span> <span>let thread_1 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 1 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>let thread_2 = std<span>::thread::</span>spawn(<span>||</span>{ </span> <span>println!("Started {}, called thread 2 {}", START_TIME.as_ref().unwrap(), Utc::now()); </span> <span>}); </span> <span>// Join threads and panic on error to show what went wrong </span> thread_1<span>.join().unwrap(); </span> thread_2<span>.join().unwrap(); </span><span>} </span>
Probieren Sie es selbst auf dem Spielplatz aus!
Die Entscheidung zwischen Ones_Cell und Lazy_static läuft im Wesentlichen darauf hinaus, welche Syntax Sie besser mögen. Wickeln Sie einfach die Zeichenfolge in einen Mutex oder Rwlock.
Im Allgemeinen neigt die Rust-Community dem Benutzer maximal Leistung-was die Dinge als Nebeneffekt komplizierter macht.
Es kann schwierig sein, alle Details im Auge zu behalten. Infolgedessen verbringe ich einen Großteil meiner Freizeit mit Rust -Funktionen, um die Möglichkeiten zu erkunden. Dabei implementiere ich normalerweise kleinere oder größere Hobbyprojekte - wie Videospiele - und lade sie in mein Github -Profil hoch. Wenn ich in meinem Experimentieren mit der Sprache etwas Interessantes finde, schreibe ich darüber in meinem privaten Blog. Überprüfen Sie das, wenn Sie mehr ausführlichere Rostinhalte lesen möchten!
FAQs, wie man globale Variablen idiomatisch in Rost verwendet
In diesem Beispiel ist global eine globale Variable des Typs I32 und wird mit dem Wert 10 initialisiert. Denken Sie daran, dass statische Variablen gelesen werden. Nur und Sie können sie nicht ändern.
Lazy_static! {
static ref Global: mutexstatic mut Global: i32 = 10;
global = 20;
println! ("{} ", Global); 🎜> Was ist die Lebensdauer einer globalen Variablen im Rost? Dies bedeutet, dass eine globale Variable erstellt wird, wenn das Programm startet und beim Ende des Programms zerstört wird.
Kann ich globale Variablen in Rostfunktionen verwenden? Funktionen. Sie sollten jedoch vorsichtig sein, wenn Sie sie verwenden, da sie zu Datenrennen führen können, wenn sie nicht ordnungsgemäß verwendet werden. Es wird im Allgemeinen empfohlen, nach Möglichkeit lokale Variablen anstelle von globalen Variablen zu verwenden.
Das obige ist der detaillierte Inhalt vonSo verwenden Sie idiomatisch globale Variablen in Rost. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!