首頁 >科技週邊 >IT業界 >如何在生鏽中習慣使用全局變量

如何在生鏽中習慣使用全局變量

Jennifer Aniston
Jennifer Aniston原創
2025-02-10 15:53:09681瀏覽

如何在生鏽中習慣使用全局變量

在生鏽中聲明和使用全局變量可能很棘手。通常,對於這種語言,生鏽可以通過強迫我們非常明確地確保魯棒性。

>

在本文中,我將討論Rust編譯器希望拯救我們的陷阱。然後,我將向您展示適用於不同方案的最佳解決方案。

鑰匙要點

    >利用``靜態''或`const'來聲明RUST中的全局變量,因為“ LET”在全局範圍內不允許。
  • >對於整體變量的線程安全運行時初始化,請考慮使用`std :: sync :: asnc :: asnc ::或諸如'lazy_static`或`armer_cell'的外部庫。
  • 由於潛在的安全問題,避免直接使用``靜態雜物'';相反,將“不安全”塊中包裝訪問或使用靜音類(如靜音詞)。
  • >對於單線程應用程序,`thread_local! 在可能的情況下,使用智能指針(例如“弧”)以共享所有權和線程安全。
  • >了解Rust的`const'和'static'之間的差異:`const'變量是嵌套和不可變的,而``靜態變量''變量可以具有可變狀態,具有內部可突變性選項,例如原子類型或互斥性。
  • >
  • 概述
  • >在Rust中實施全球狀態有很多選擇。如果您很著急,這是我建議的快速概述。
  • >

您可以通過以下鏈接跳到本文的特定部分:>

無全球:弧 / rc

的重構 如何在生鏽中習慣使用全局變量編譯時初始化的全球:start t / static t

使用外部庫來輕鬆運行時初始化的全球範圍:lazy_static / asher_cell

    >實現自己的運行時初始化:std :: Sync ::一次靜態mut t
  • >單線程運行時初始化的專業案例:thread_local
  • >天真的首次嘗試使用Rust
  • 中的全局變量
  • >讓我們從如何不使用全局變量的示例開始。假設我想將程序的啟動時間存儲在全局字符串中。稍後,我想從多個線程訪問值。
  • >使用LET,可能會像Rust中的任何其他變量一樣聲明一個全局變量。然後,完整的程序可以看起來像這樣:

>在操場上自己嘗試!

這是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>
hm,因此在運行時無法計算靜態變量的初始化值。那也許只是讓它不可原始化?

>

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>
這個錯誤並不是很明顯。編譯器告訴我們,生成的線程的壽命可能比值啟動_Time的壽命更長,該值始於主函數的堆棧框架。

> 從技術上講,我們可以看到這是不可能的。將線程連接在一起,因此主線程不會在子螺紋完成之前退出。

,但編譯器不夠聰明,無法弄清楚這種特殊情況。通常,當產生新線程時,所提供的閉合只能藉用靜態壽命借用物品。換句話說,借來的價值必須在整個程序壽命中還活著。
<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>
>在操場上自己嘗試!

>這是關於如何在線程之間共享狀態的同時避免全局變量的快速分析。除了我到目前為止我向您展示的內容之外,您可能還需要內部可變性來修改共享狀態。內部突變性的全部覆蓋範圍不在本文的範圍之內。但是在這個特殊的示例中,我會選擇弧>將線程安全的內部變形可添加到start_time。

在編譯時間已知全局變量值時

根據我的經驗,全球狀態的最常見用例不是變量,而是常數。在Rust中,它們有兩種口味:

常數值,用const定義。這些是由編譯器夾住的。內部可變性永遠不會允許。
  • 靜態變量,用靜態定義。他們在數據段中獲得固定空間。內部可變性是可能的。
  • >可以用編譯時常數初始化它們。這些可能是簡單的價值觀,例如42或“ Hello World”。或者它可能是涉及其他幾個編譯時常數和標記為const的函數的表達式。只要我們避免循環依賴性。 (您可以在Rust參考中找到有關恆定表達式的更多詳細信息。)
>

通常,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>
>如果您需要內部可突變性,則有幾種選擇。對於大多數原語,STD :: Sync :: Atomic中都有相應的原子變體。它們提供了乾淨的API,以原子上加載,存儲和更新值。

>在沒有原子的情況下,通常的選擇是鎖。 Rust的Standard Library提供了讀寫鎖(RWLOCK)和相互排除鎖(Mutex)。

> 但是,如果您需要在運行時計算值,或者需要堆分配,則const和static沒有幫助。

> RUST中的RUST中的單線讀取全局變量,運行時初始化>

我寫的大多數應用程序只有一個線程。在這種情況下,不需要鎖定機制。

但是,僅僅因為只有一個線程,我們不應該直接使用靜態mut並將訪問包裹在不安全中。這樣,我們可能最終會造成嚴重的記憶腐敗。

例如,從全局變量借用不安全可能會同時為我們提供多個可變的參考。然後,我們可以使用其中一個迭代向量,而另一個則從同一向量中刪除值。然後,迭代器可以超越有效的內存邊界,這是安全銹所阻止的潛在崩潰。

>但是,標準庫有一種“全球”存儲價值的方法,以在單個線程中安全訪問。我說的是當地人。在存在許多線程的情況下,每個線程都會獲得該變量的獨立副本。但是在我們的情況下,只有一個線程,只有一個副本。

> 用thread_local創建

線程當地人!宏。訪問它們需要使用閉合,如以下示例所示:>

>並不是所有解決方案中最簡單的。但是它允許我們執行任意初始化代碼,該代碼將在第一次訪問該值時及時運行。 在內部可突變性方面,

線程非常好。與所有其他解決方案不同,它不需要同步。這允許使用Refcell進行內部突變性,從而避免了靜音的鎖定頭頂。

線程局部的絕對性能高度取決於平台。但是我在自己的PC上進行了一些快速測試,將其與依靠鎖的內部變異性進行了比較,並發現它的速度快10倍。我不希望結果會在任何平台上翻轉,但是如果您非常關心性能,請確保運行自己的基準。

這是如何使用revcell進行內部變異性的一個示例:

>在操場上自己嘗試!

>%0A

%E5%A6%82%E6%9E%9C%E6%82%A8%E6%AD%A3%E5%9C%A8%E5%B0%8B%E6%89%BE%E6%9B%B4%E7%B0%A1%E5%96%AE%E7%9A%84%E6%9D%B1%E8%A5%BF%EF%BC%8C%E6%88%91%E5%8F%AF%E4%BB%A5%E5%BC%B7%E7%83%88%E6%8E%A8%E8%96%A6%E5%85%A9%E5%80%8B%E6%9D%BF%E6%A2%9D%E7%AE%B1%E4%B9%8B%E4%B8%80%EF%BC%8C%E6%88%91%E5%B0%87%E5%9C%A8%E4%B8%8B%E4%B8%80%E7%AF%80%E4%B8%AD%E9%80%B2%E8%A1%8C%E8%A8%8E%E8%AB%96%E3%80%82%0A

>%E7%94%A8%E6%96%BC%E7%AE%A1%E7%90%86RUST%20%E4%B8%AD%E5%85%A8%E5%B1%80%E8%AE%8A%E9%87%8F%E7%9A%84%E5%A4%96%E9%83%A8%E5%BA%AB%0A>%E5%9F%BA%E6%96%BC%E5%8F%97%E6%AD%A1%E8%BF%8E%E7%A8%8B%E5%BA%A6%E5%92%8C%E5%80%8B%E4%BA%BA%E5%93%81%E5%91%B3%EF%BC%8C%E6%88%91%E6%83%B3%E6%8E%A8%E8%96%A6%E5%85%A9%E5%80%8B%E5%BA%AB%EF%BC%8C%E6%88%91%E8%AA%8D%E7%82%BA%E9%80%99%E6%98%AF%E6%88%AA%E8%87%B32021%E5%B9%B4%E7%9A%84Rust%E6%98%93%E6%96%BC%E5%85%A8%E7%90%83%E8%AE%8A%E9%87%8F%E7%9A%84%E6%9C%80%E4%BD%B3%E9%81%B8%E6%93%87%E3%80%82%0A

>%E7%95%B6%E5%89%8D%E8%80%83%E6%85%AE%E4%BA%86%E6%A8%99%E6%BA%96%E5%BA%AB%E7%9A%84%E5%96%AE%E5%85%83%E6%A0%BC%E3%80%82%20%EF%BC%88%E8%AB%8B%E5%8F%83%E9%96%B1%E6%AD%A4%E8%B7%9F%E8%B8%AA%E5%95%8F%E9%A1%8C%E3%80%82%EF%BC%89%E5%A6%82%E6%9E%9C%E6%82%A8%E5%9C%A8%E6%AF%8F%E6%99%9A%E7%9A%84%E7%B7%A8%E8%AD%AF%E5%99%A8%E4%B8%AD%EF%BC%8C%E5%89%87%E5%8F%AF%E4%BB%A5%E9%80%9A%E9%81%8E%E5%B0%87%EF%BC%83%EF%BC%81%20%5Bfeature%EF%BC%88bare_cell%EF%BC%89%5D%E6%B7%BB%E5%8A%A0%E5%88%B0%E9%A0%85%E7%9B%AE%E7%9A%84%E4%B8%BB%E7%AE%A1%E4%B8%AD%E3%80%82%0A

%E9%80%99%E6%98%AF%E4%B8%80%E5%80%8B%E5%9C%A8%E7%A9%A9%E5%AE%9A%E7%B7%A8%E8%AD%AF%E5%99%A8%E4%B8%8A%E4%BD%BF%E7%94%A8ANSER_CELL%E7%9A%84%E7%A4%BA%E4%BE%8B%EF%BC%8C%E5%85%B7%E6%9C%89%E9%A1%8D%E5%A4%96%E7%9A%84%E4%BE%9D%E8%B3%B4%E6%80%A7%EF%BC%9A

%0A

%0A

>%E5%9C%A8%E6%93%8D%E5%A0%B4%E4%B8%8A%E8%87%AA%E5%B7%B1%E5%98%97%E8%A9%A6%EF%BC%81%20

%0Ause%20chrono::Utc;%0A%0Alet%20START_TIME%20=%20Utc::now().to_string();%0A%0Apub%20fn%20main()%20%7B%0A%20%20%20%20let%20thread_1%20=%20std::thread::spawn(%7C%7C%7B%0A%20%20%20%20%20%20%20%20println!(" started called thread start_time.as_ref utc::now> }); let thread_2 = std::thread::spawn(||{ println!("Started {}, called thread 2 {}", START_TIME.as_ref().unwrap(), Utc::now()); }); // Join threads and panic on error to show what went wrong thread_1.join().unwrap(); thread_2.join().unwrap(); }

>

結論

這些都是我知道的所有(明智)實現全球變量的方法。我希望這更簡單。但是全球狀態本質上是複雜的。結合Rust的記憶安全保證,似乎不可能一個簡單的捕獲式解決方案。但是我希望這篇文章能幫助您通過大量可用選項查看。

>通常,生鏽社區傾向於為用戶賦予最大的力量 - 這使事情變得更加複雜。

>很難跟踪所有細節。結果,我花了很多空閒時間來探索可能性。在此過程中,我通常會實施較小或大型的愛好項目(例如視頻遊戲),然後將其上傳到我的GitHub個人資料中。然後,如果我在對語言的實驗中發現一些有趣的東西,我會在私人博客上寫下有關它的文章。檢查一下您是否想閱讀更多深入的生鏽內容!

> 關於如何慣用地使用RUST

中的全局變量的FAQS

>在生鏽中靜態和const之間有什麼區別? Rust中的const是一個恆定的值,這意味著它不會改變。它類似於變量,但其值是恆定的,不能更改。另一方面,Rust中的靜態變量是程序二進制的“僅閱讀數據”部分中存儲的全局變量。這是整個程序中可用的變量,而不僅僅是聲明的範圍。與const不同,靜態變量在內存中具有固定的地址。

>如何在rust中聲明Rust的全局變量?以下是一個示例:

靜態全局:i32 = 10;

在此示例中,global是類型I32的全局變量,並使用值10進行了初始化。請記住,讀取靜態變量是讀取的 - 只有您無法修改它們。

>我可以修改Rust的全局變量嗎?這是Rust的安全功能,可以防止在編譯時進行數據競賽。但是,您可以在std :: Sync模塊中使用Mutex或rwlock來實現可變的全局變量。

!這意味著將靜態變量的初始化延遲,直到首次訪問。當初始化很昂貴,並且您只想在必要時進行此操作時,這可能很有用。您如何使用“ lazy_static!”的一個示例來聲明Rust中的全局變量:

#[macro_use]

extern crate lazy_static; 使用std :: sync :: sync :: sync :: mutex;

lazy_static! {

靜態參考全局:mutex = mutex :: new(new(0);

}

}

在此示例中,global是類型Mutex 的全局變量,並用使用值0。“ lazy_static!”可確保初始化是以線程安全的方式進行的。 >
在Rust中,“靜態”用於聲明僅讀取的全局變量,而“靜態mut”用於聲明可變的全局變量。但是,“靜態mut”是不安全的,因為如果兩個線程同時訪問變量,它可能會導致不確定的行為。
>
>我如何安全地訪問rust中的'static mut'變量? >您可以使用“不安全”的關鍵字安全地訪問Rust中的“靜態MUT”變量。這是一個示例:

靜態mut global:i32 = 10;

fn main(){
> unsafe {

global = 20;

println!(“ {}}) “,global);

}

}

>

在此示例中,使用'Undafe'關鍵字來指示代碼可以執行在安全代碼中未定義的操作。

RUST中的全局變量的壽命是什麼?這意味著當程序啟動並在程序結束時被破壞時會創建全局變量。


>我可以在Rust函數中使用全局變量嗎?
是的,您可以在Rust中使用全局變量功能。但是,使用它們時應該要小心,因為如果不正確使用,它們可以導致數據競賽。通常建議盡可能使用本地變量而不是全局變量。

> RUST中的全局變量的替代方法是什麼?

>如果要在Rust程序的不同部分之間共享數據,則可以使用多種替代替代全局變量的替代方法。這些包括將數據作為函數參數,使用函數返回數據,使用諸如結構和枚舉之類的數據結構以及使用頻道和鎖等並發原始的數據。

>。

以上是如何在生鏽中習慣使用全局變量的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn