ホームページ >テクノロジー周辺機器 >IT業界 >さびにグローバル変数をディオマクタックに使用する方法
錆でグローバル変数を宣言して使用するのは難しい場合があります。通常、この言語では、錆は私たちを非常に明確にすることで堅牢性を保証します。 この記事では、Rust Compilerが私たちを救いたい落とし穴について説明します。次に、さまざまなシナリオで利用できる最高のソリューションを紹介します。
キーテイクアウト
外部ライブラリを使用してランタイムの初期化されたグローバルを簡単に使用してください:lazy_static / wons_cell
独自のランタイム初期化を実装:std :: sync :: and static mut t
シングルスレッドランタイム初期化のための特殊なケース:thread_local
錆のグローバル変数を使用する素朴な最初の試み
これは錆の無効な構文です。 LETキーワードは、グローバル範囲で使用できません。静的またはconstのみを使用できます。後者は、変数ではなく、真の定数を宣言します。静的のみがグローバル変数を提供します。
この背後にある理由は、実行時にスタック上の変数を割り当てることです。 let t = box :: new();生成されたマシンコードには、スタックに保存されるヒープへのポインターがまだあります。
グローバル変数は、プログラムのデータセグメントに保存されます。実行中に変更されない固定アドレスがあります。したがって、コードセグメントには一定のアドレスを含めることができ、スタックにはまったくスペースが必要ありません。わかりました、なぜ別の構文が必要なのか理解できます。 Rustは、最新のシステムプログラミング言語として、メモリ管理について非常に明示的になりたいと考えています。
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>hm、したがって、静的変数の初期化値は、実行時に計算することはできません。それから多分それを単位化しないようにしますか?
<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>それでもうまくいきません!すべての静的値は、ユーザーコードが実行される前に完全に初期化され、有効にする必要があります。
JavaScriptやPythonなどの別の言語からRustに来ている場合、これは不必要に制限されるように思えるかもしれません。しかし、Cの第一人者は、静的初期化順序FIASCOについてのストーリーを伝えることができます。
たとえば、このようなものを想像してください:<span>use chrono<span>::</span>Utc; </span> <span>static START_TIME; </span> <span>pub fn main() { </span> <span>// ... </span><span>} </span>
このコードスニペットでは、円形の依存関係により、安全な初期化順序はありません。 安全性を気にしないcである場合、結果はA:1 b:1 c:2になります。コードが実行される前にゼロイニタイアル化し、順序は上からボトムから定義されます各コンピレーションユニット内。
少なくとも結果が何であるかを定義しています。ただし、「Fiasco」は、静的変数が異なる.cppファイルからのものであるため、異なるコンピレーションユニットからのもので始まります。次に、注文は未定義であり、通常、コンパイルコマンドラインのファイルの順序に依存します。
錆では、ゼロイナイト化は物ではありません。結局のところ、ZeroはBoxなどの多くのタイプの無効な値です。さらに、錆では、奇妙な注文の問題を受け入れません。安全でない限り、コンパイラは正気なコードを書くことのみを許可する必要があります。そして、それがコンパイラが私たちが簡単なランタイムの初期化を使用することを妨げる理由です。しかし、null-pointerに相当するものを使用しないで初期化を回避できますか?少なくともこれはすべて錆タイプのシステムに従っています。確かに、初期化をメイン関数の一番上に移動することができますか?
ああ、まあ、私たちが得るエラーは…
<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>このエラーはまったく明白ではありません。コンパイラは、生成されたスレッドは、メイン関数のスタックフレームに存在する値start_timeよりも長く生きている可能性があることを教えています。
技術的には、これは不可能であることがわかります。スレッドが結合されるため、子スレッドが終了する前にメインスレッドは終了しません。
しかし、コンパイラはこの特定のケースを把握するほど賢くありません。一般に、新しいスレッドが生成されると、提供された閉鎖は静的な寿命でのみアイテムを借りることができます。言い換えれば、借りた価値は完全なプログラムの生涯のために生きているに違いありません。<span>use chrono<span>::</span>Utc; </span> <span>static START_TIME; </span> <span>pub fn main() { </span> <span>// ... </span><span>} </span>
錆について学んでいる人にとっては、これがグローバル変数に手を差し伸べたいポイントかもしれません。しかし、それよりもはるかに簡単なソリューションは少なくとも2つあります。最も簡単なのは、文字列値をクローンしてから、文字列の所有権を閉鎖に移動することです。もちろん、それには追加の割り当てといくつかの追加のメモリが必要です。しかし、この場合、それはただの短い文字列であり、パフォーマンスが批判的ではありません。
しかし、共有するのがはるかに大きなオブジェクトだったらどうでしょうか?クローンを作成したくない場合は、参照がカウントされたスマートポインターの後ろにラップしてください。 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>遊び場で自分で試してみてください!
これは、グローバル変数を避けながら、スレッド間で状態を共有する方法についての迅速な概要でした。私がこれまでにあなたに示したことを超えて、共有状態を変更するためにインテリアの可変性も必要かもしれません。インテリアの可変性の完全なカバレッジは、この記事の範囲外です。しかし、この特定の例では、arc
コンパイル時にグローバル変数値がわかっている場合
私の経験では、グローバル状態の最も一般的なユースケースは変数ではなく定数です。さびには、2つのフレーバーがあります:静的変数、静的変数。データセグメントに固定スペースを受け取ります。内部の変化が可能です。
Atomicsがない場合、通常の選択はロックです。 Rustの標準ライブラリは、読み取りワイトロック(RWLock)と相互除外ロック(Mutex)を提供しています。
ただし、実行時に値を計算する必要がある場合、またはヒープ配分が必要な場合は、constとstaticは役に立ちません。 ランタイムの初期化を伴う錆の単一スレッドグローバル変数私が書いているほとんどのアプリケーションには、単一のスレッドのみがあります。その場合、ロックメカニズムは必要ありません ただし、Static Mutを直接使用して、1つのスレッドしかないからといって、アクセスを安全ではないままにしてはいけません。このようにして、私たちは深刻な記憶の腐敗に終わる可能性があります。
スレッドの地元の人はthread_localで作成されます!マクロ。次の例に示すように、それらにアクセスするには、閉鎖の使用が必要です。
すべてのソリューションの中で最も単純なものではありません。ただし、任意の初期化コードを実行することができます。これは、値への最初のアクセスが発生したときに実行されます。
スレッドロカルは、インテリアの可変性に関しては本当に良いです。他のすべてのソリューションとは異なり、同期する必要はありません。これにより、refcellを使用してインテリアの可変性を使用して、ミューテックスのロックオーバーヘッドを回避できます。
スレッドロカルの絶対パフォーマンスは、プラットフォームに大きく依存しています。しかし、私は自分のPCでいくつかのクイックテストを行い、それをロックに依存するインテリアの可変性と比較して、10倍高速であることがわかりました。私は結果がどのプラットフォームでひっくり返るとは思わないが、パフォーマンスに深く関心を持っている場合は、必ず独自のベンチマークを実行するようにしてください。室内の可変性にrefcellを使用する方法の例を次に示します:
遊び場で自分で試してみてください!
<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>サイドノートとして、WebAssemblyのスレッドはx86_64プラットフォーム上のスレッドとは異なりますが、このパターンはthread_localです! Refcellは、ブラウザで実行するためにRustをコンパイルするときにも適用できます。マルチスレッドコードに安全なアプローチを使用すると、その場合はやり過ぎです。 (ブラウザ内でRustを実行するというアイデアが新しい場合は、「Rust Tutorial:An Rust for JavaScript Devs」という以前の記事をお気軽にお読みください。)
スレッドロカルに関する1つの注意点は、その実装がプラットフォームに依存することです。通常、これはあなたが気づくものではありませんが、そのためにドロップセマンティックがプラットフォームに依存していることに注意してください。
とはいえ、マルチスレッドグローバルのソリューションは、明らかに単一スレッドケースでも機能します。そして、インテリアの可変性がなければ、それらはスレッドロカルと同じくらい速いようです。
それでは、次のことを見てみましょう。
ランタイム初期化を備えたマルチスレッドグローバル変数標準ライブラリには現在、ランタイム初期化を伴う安全なグローバル変数に対する優れたソリューションがありません。ただし、STD :: Sync ::を使用すると、自分が何をしているのかを知っている場合、安全で安全に使用するものを構築することが可能です。
遊び場で自分で試してみてください!
より簡単なものを探している場合は、次のセクションで説明します。
錆のグローバル変数を管理するための外部ライブラリ現在、セルが標準ライブラリで検討されています。 (この追跡の問題を参照してください。)毎晩のコンパイラを使用している場合は、プロジェクトのmain.Rs.
に#![feature(and_cell)]を追加して、不安定なAPIを使用することができます。安定したコンパイラでand_cellを使用した例を次に示します。
遊び場で自分で試してみてください!
<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>
以上がさびにグローバル変数をディオマクタックに使用する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。