>
在本文中,我將討論Rust編譯器希望拯救我們的陷阱。然後,我將向您展示適用於不同方案的最佳解決方案。鑰匙要點
無全球:弧 / rc
的重構
編譯時初始化的全球:start t / static t
使用外部庫來輕鬆運行時初始化的全球範圍:lazy_static / asher_cell
這是Rust的無效語法。 LET關鍵字不能在全局範圍中使用。我們只能使用靜態或const。後者聲明一個真正的常數,而不是變量。只有靜態才能給我們一個全局變量。
>在此背後的原因是在運行時分配堆棧上的變量。請注意,當在堆上分配時,這仍然是正確的,如讓t = box :: new();。在生成的機器代碼中,仍然有一個指針進入堆上的堆中。
>>全局變量存儲在程序的數據段中。他們的固定地址在執行過程中不會改變。因此,代碼段可以包含常數地址,並且根本不需要堆棧上的空間。
>好吧,我們可以理解為什麼需要其他語法。作為一種現代系統編程語言,Rust希望對內存管理非常明確。
>讓我們再試一次:
<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>
>
<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>
>
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>
<span>use chrono<span>::</span>Utc; </span> <span>static START_TIME; </span> <span>pub fn main() { </span> <span>// ... </span><span>} </span>
>
如果您要從其他語言(例如JavaScript或Python)生鏽,這似乎是不必要的限制。但是,任何C大師都可以告訴您有關靜態初始化順序慘敗的故事,如果我們不小心,這可能會導致不確定的初始化順序。> 例如,想像一下這樣的東西:
>在此代碼段中,由於循環依賴性,沒有安全的初始化順序。
<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>
如果是C(不關心安全性),結果將為a:1 b:1 c:2。在每個彙編單元中。
至少它定義了結果。但是,當靜態變量來自不同的.cpp文件,因此“慘案”開始時,“慘敗”就開始了。然後,該順序是未定義的,通常取決於彙編命令行中文件的順序。
>在銹病中,零判決不是一回事。畢竟,零是許多類型(例如框)的無效值。此外,在生鏽中,我們不接受奇怪的訂購問題。只要我們遠離不安全,編譯器應該只允許我們編寫理智代碼。這就是編譯器阻止我們使用直接運行時初始化的原因。
>>但是,我可以通過不使用NONE(等效零件)來規避初始化嗎?至少這與Rust類型系統一致。當然,我可以將初始化移至主函數的頂部,對嗎?
<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>
>
在這一點上,我可以將其包裹在一個不安全的{...}塊中,並且可以工作。有時,這是一個有效的策略。也許要測試剩餘的代碼是否按預期工作。但這不是我想向您展示的慣用解決方案。因此,讓我們探索編譯器保證安全的解決方案。<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>
重構示例
唯一的問題是藉用檢查器:
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>
> 從技術上講,我們可以看到這是不可能的。將線程連接在一起,因此主線程不會在子螺紋完成之前退出。
,但編譯器不夠聰明,無法弄清楚這種特殊情況。通常,當產生新線程時,所提供的閉合只能藉用靜態壽命借用物品。換句話說,借來的價值必須在整個程序壽命中還活著。<span>use chrono<span>::</span>Utc; </span> <span>static START_TIME; </span> <span>pub fn main() { </span> <span>// ... </span><span>} </span>
對於任何僅了解Rust的人來說,這可能是您想要與全球變量聯繫的地步。但是至少有兩種解決方案比這更容易。最簡單的是克隆字符串值,然後將字符串的所有權移至封閉中。當然,這需要額外的分配和一些額外的內存。但是在這種情況下,這只是一個簡短的字符串,沒有任何關鍵性能。
>>但是,如果它是一個更大的對象,該怎麼辦?如果您不想克隆它,請將其包裹在參考註銷的智能指針後面。 RC是單線程引用計數類型。 ARC是可以在線程之間安全共享值的原子版。
因此,為了滿足編譯器,我們可以使用弧線如下:
<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>
>這是關於如何在線程之間共享狀態的同時避免全局變量的快速分析。除了我到目前為止我向您展示的內容之外,您可能還需要內部可變性來修改共享狀態。內部突變性的全部覆蓋範圍不在本文的範圍之內。但是在這個特殊的示例中,我會選擇弧
在編譯時間已知全局變量值時
常數值,用const定義。這些是由編譯器夾住的。內部可變性永遠不會允許。
通常,const是更好的選擇 - 除非您需要內部可變性,否則您特別想避免內部。
<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>
>在沒有原子的情況下,通常的選擇是鎖。 Rust的Standard Library提供了讀寫鎖(RWLOCK)和相互排除鎖(Mutex)。
> 但是,如果您需要在運行時計算值,或者需要堆分配,則const和static沒有幫助。
> RUST中的RUST中的單線讀取全局變量,運行時初始化我寫的大多數應用程序只有一個線程。在這種情況下,不需要鎖定機制。
但是,僅僅因為只有一個線程,我們不應該直接使用靜態mut並將訪問包裹在不安全中。這樣,我們可能最終會造成嚴重的記憶腐敗。> 用thread_local創建
線程當地人!宏。訪問它們需要使用閉合,如以下示例所示:>並不是所有解決方案中最簡單的。但是它允許我們執行任意初始化代碼,該代碼將在第一次訪問該值時及時運行。 在內部可突變性方面,
線程非常好。與所有其他解決方案不同,它不需要同步。這允許使用Refcell進行內部突變性,從而避免了靜音的鎖定頭頂。
線程局部的絕對性能高度取決於平台。但是我在自己的PC上進行了一些快速測試,將其與依靠鎖的內部變異性進行了比較,並發現它的速度快10倍。我不希望結果會在任何平台上翻轉,但是如果您非常關心性能,請確保運行自己的基準。這是如何使用revcell進行內部變異性的一個示例:
>在操場上自己嘗試!
以上是如何在生鏽中習慣使用全局變量的詳細內容。更多資訊請關注PHP中文網其他相關文章!