ホームページ >テクノロジー周辺機器 >IT業界 >さびにグローバル変数をディオマクタックに使用する方法

さびにグローバル変数をディオマクタックに使用する方法

Jennifer Aniston
Jennifer Anistonオリジナル
2025-02-10 15:53:09681ブラウズ

さびにグローバル変数をディオマクタックに使用する方法

錆でグローバル変数を宣言して使用するのは難しい場合があります。通常、この言語では、錆は私たちを非常に明確にすることで堅牢性を保証します。 この記事では、Rust Compilerが私たちを救いたい落とし穴について説明します。次に、さまざまなシナリオで利用できる最高のソリューションを紹介します。

キーテイクアウト

錆のグローバル変数を宣言するために「静的」または「const」を利用します。 グローバル変数のスレッドセーフランタイム初期化については、 `std :: sync :: and`または` lazy_static`や `and_cell`。

潜在的な安全性の問題のために「静的ムット」を直接使用しないでください。代わりに、「Unsafe」ブロック内でアクセスをラップするか、ミューテックスのような同期プリミティブを使用します。
    シングルスレッドアプリケーションの場合、 `thread_local!`マクロは、同期を必要とせずにグローバルな状態を処理する安全な方法を提供できます。
  • グローバル変数を可能な限りローカルスコープにリファクタリングし、共有所有権とスレッドの安全性のために「arc」のようなスマートポインターを使用しています。
  • 錆の「const」と「静的」の違いを理解する: `const`変数はインラインで不変であるが、「静的」変数は、原子タイプやミューテックスなどの内部可変オプションを持つ可変状態を持つことができる。
  • 概要
  • 錆にグローバルな状態を実装するための多くのオプションがあります。急いでいる場合は、私の推奨事項の簡単な概要をご覧ください。
  • 次のリンクを使用して、この記事の特定のセクションにジャンプできます。
  • グローバルなし:arc / rc
をリファクタリングします

コンパイル時間初期化されたグローバル:const t / static t

外部ライブラリを使用してランタイムの初期化されたグローバルを簡単に使用してください:lazy_static / wons_cell

独自のランタイム初期化を実装:std :: sync :: and static mut tさびにグローバル変数をディオマクタックに使用する方法 シングルスレッドランタイム初期化のための特殊なケース:thread_local

錆のグローバル変数を使用する素朴な最初の試み
  • グローバル変数を使用しない方法の例から始めましょう。プログラムの開始時間をグローバル文字列に保存したいと仮定します。後で、複数のスレッドから値にアクセスしたい。
  • 錆の初心者は、retを使用して、錆の他の変数とまったく同じようにグローバル変数を宣言したいと思われるかもしれません。完全なプログラムは次のようになる可能性があります:
  • 遊び場で自分で試してみてください!
  • これは錆の無効な構文です。 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 >を選択して、starch-safeインテリアの可変性をstart_timeに追加します。

    コンパイル時にグローバル変数値がわかっている場合

    私の経験では、グローバル状態の最も一般的なユースケースは変数ではなく定数です。さびには、2つのフレーバーがあります:

    Constで定義された一定の値。これらはコンパイラによってインラードされています。室内の可変性は決して許可されません。

    静的変数、静的変数。データセグメントに固定スペースを受け取ります。内部の変化が可能です。

    • 両方とも、コンパイル時間定数で初期化できます。これらは、42や「Hello World」などの単純な値である可能性があります。または、constとしてマークされた他のいくつかのコンパイル時間定数と関数を含む式である可能性があります。円形の依存関係を避けている限り。 (Rust Referenceの一定の式の詳細を見つけることができます。)
    • 通常、constはより良い選択です - 室内の可変性が必要な場合、または具体的にはインランスを避けたいのです。
    インテリアの可変性が必要な場合、いくつかのオプションがあります。ほとんどのプリミティブの場合、STD :: Sync :: Atomicで利用可能な対応する原子バリアントがあります。それらは、値を原子的にロード、保存、および更新するためのクリーンAPIを提供します。

    Atomicsがない場合、通常の選択はロックです。 Rustの標準ライブラリは、読み取りワイトロック(RWLock)と相互除外ロック(Mutex)を提供しています。

    ただし、実行時に値を計算する必要がある場合、またはヒープ配分が必要な場合は、constとstaticは役に立ちません。 ランタイムの初期化を伴う錆の単一スレッドグローバル変数

    私が書いているほとんどのアプリケーションには、単一のスレッドのみがあります。その場合、ロックメカニズムは必要ありません ただし、Static Mutを直接使用して、1つのスレッドしかないからといって、アクセスを安全ではないままにしてはいけません。このようにして、私たちは深刻な記憶の腐敗に終わる可能性があります。

    たとえば、グローバル変数から安全でないと同時に、複数の可変参照を提供する可能性があります。その後、そのうちの1つを使用してベクトルを反復し、別のものを同じベクトルから削除することができます。その後、イテレーターは有効なメモリの境界を超えて、安全な錆が防止されていた潜在的なクラッシュを越えて行くことができます。

    しかし、標準的なライブラリには、単一のスレッド内の安全なアクセスのために値を「グローバルに」保存する方法があります。私はスレッドの地元の人々について話している。多くのスレッドが存在する場合、各スレッドは変数の独立したコピーを取得します。しかし、私たちの場合、単一のスレッドでは、コピーが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 ::を使用すると、自分が何をしているのかを知っている場合、安全で安全に使用するものを構築することが可能です。

    公式文書の例は良い出発点です。インテリアの可変性も必要な場合は、そのアプローチを読み取りワイトロックまたはミューテックスと組み合わせる必要があります。それがどのように見えるかは次のとおりです

    遊び場で自分で試してみてください!

    より簡単なものを探している場合は、次のセクションで説明します。

    錆のグローバル変数を管理するための外部ライブラリ

    人気と個人的な好みに基づいて、2021年現在、錆の簡単なグローバル変数に最適な選択肢であると思う2つのライブラリをお勧めしたいと思います。

    現在、セルが標準ライブラリで検討されています。 (この追跡の問題を参照してください。)毎晩のコンパイラを使用している場合は、プロジェクトの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>

    and_cellとlazy_staticの間の決定は、本質的にあなたが好きな構文をより沸騰させます。文字列をミューテックスまたはrwlockに包むだけです。

    結論

    これらは、私が知っている錆にグローバル変数を実装するすべての(賢明な)方法です。よりシンプルだったらいいのに。しかし、グローバルな状態は本質的に複雑です。 Rustのメモリ安全保証と組み合わせて、単純なキャッチセムすべてのソリューションは不可能であると思われます。しかし、この記事があなたが利用可能な多くのオプションを見るのに役立つことを願っています。

    一般的に、錆びたコミュニティはユーザーに最大のパワーを与える傾向があります。これにより、副作用として物事がより複雑になります。

    すべての詳細を追跡するのは難しい場合があります。その結果、私は自由な時間の多くを錆の特徴で遊んで、可能性を探求しています。その過程で、私は通常、ビデオゲームなどの小規模または大規模な趣味プロジェクトを実装し、GitHubプロファイルにアップロードします。それから、言語の実験で何か面白いことを見つけたら、私はそれについて私のプライベートブログに書きます。詳細な錆コンテンツをもっと読みたい場合は、それをチェックしてください!

    錆びにグローバル変数をディオマンに使用する方法についてのFAQ

    錆の静的とconstの違いは何ですか?錆の

    rustの違いは、静的変数とconstの両方がグローバル変数を宣言するために使用されますが、異なる特性があります。錆のconstは一定の値です。つまり、変化しない値です。変数に似ていますが、その値は一定であり、変更することはできません。一方、Rustの静的変数は、プログラムのバイナリの読み取り専用データセクションに保存されているグローバル変数です。これは、宣言された範囲だけでなく、プログラム全体で利用可能な変数です。 constとは異なり、静的変数はメモリに固定アドレスを持っています。

    錆のグローバル変数を宣言するにはどうすればよいですか?例は次のとおりです。

    静的グローバル:i32 = 10;

    この例では、グローバルはタイプI32のグローバル変数であり、値10で初期化されます。静的変数は読み取られることを忘れないでください。単独でそれらを変更することはできません。

    rustのグローバル変数を変更できますか?

    いいえ、デフォルトでは不変であるため、錆のグローバル変数を直接変更することはできません。これは、コンパイル時にデータレースを防ぐための錆の安全性の特徴です。ただし、STD :: SyncモジュールでMutexまたはRWLockを使用して、変速可能なグローバル変数を実現できます錆びたマクロでは、静的変数を怠zyな方法で初期化できます。これは、静的変数の初期化が初めてアクセスされるまで遅延することを意味します。これは、初期化が高価であり、必要な場合にのみ実行したい場合に役立ちます「lazy_static!」を使用して、錆のグローバル変数を宣言する方法の例:

    #[macro_use]

    lazy_static! { = Mutex :: new(0);

    }

    この例では、グローバルは型mutex のグローバル変数であり、で初期化されます。値0。「lazy_static!」 >

    Rustでは、「Static」を使用して読み取り専用のグローバル変数を宣言し、「Static Mut」は変動可能なグローバル変数を宣言するために使用されます。ただし、2つのスレッドが変数に同時にアクセスすると未定義の動作を引き起こす可能性があるため、「static mut」は安全ではありません。 >「安全でない」キーワードを使用して、Rustの「静的MUT」変数に安全にアクセスできます。例は次のとおりです。

    static mut global:i32 = 10;

    fn main(){
    unsafe {
    global = 20;
    println!( "{} "、global);
    }
    }
    }

    この例では、「安全でない」キーワードは、コードが安全なコードで定義されていない操作を実行できることを示すために使用されます。 🎜>錆のグローバル変数の寿命は何ですか?

    錆のグローバル変数の寿命は、プログラムの全期間です。これは、プログラムが開始されたときにグローバル変数が作成され、プログラムが終了したときに破壊されることを意味します。関数。ただし、適切に使用しないとデータレースにつながる可能性があるため、使用する場合は注意する必要があります。一般に、可能な限りグローバル変数の代わりにローカル変数を使用することをお勧めします。錆のグローバル変数の代替品は何ですか?

    ​​

    Rustプログラムのさまざまな部分間でデータを共有する場合は、グローバル変数のいくつかの選択肢を使用できます。これらには、関数の引数としてデータを渡すこと、関数からのデータの返却、構造や酵素などのデータ構造の使用、およびチャネルやロックなどの並行性プリミティブの使用が含まれます。

以上がさびにグローバル変数をディオマクタックに使用する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。