首页 > 科技周边 > IT业界 > 如何在生锈中习惯使用全局变量

如何在生锈中习惯使用全局变量

Jennifer Aniston
发布: 2025-02-10 15:53:09
原创
699 人浏览过

如何在生锈中习惯使用全局变量

在生锈中声明和使用全局变量可能很棘手。通常,对于这种语言,生锈可以通过强迫我们非常明确地确保鲁棒性。

>

在本文中,我将讨论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%AF%BB%E6%89%BE%E6%9B%B4%E7%AE%80%E5%8D%95%E7%9A%84%E4%B8%9C%E8%A5%BF%EF%BC%8C%E6%88%91%E5%8F%AF%E4%BB%A5%E5%BC%BA%E7%83%88%E6%8E%A8%E8%8D%90%E4%B8%A4%E4%B8%AA%E6%9D%BF%E6%9D%A1%E7%AE%B1%E4%B9%8B%E4%B8%80%EF%BC%8C%E6%88%91%E5%B0%86%E5%9C%A8%E4%B8%8B%E4%B8%80%E8%8A%82%E4%B8%AD%E8%BF%9B%E8%A1%8C%E8%AE%A8%E8%AE%BA%E3%80%82%0A

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

>%E5%BD%93%E5%89%8D%E8%80%83%E8%99%91%E4%BA%86%E6%A0%87%E5%87%86%E5%BA%93%E7%9A%84%E5%8D%95%E5%85%83%E6%A0%BC%E3%80%82%20%EF%BC%88%E8%AF%B7%E5%8F%82%E9%98%85%E6%AD%A4%E8%B7%9F%E8%B8%AA%E9%97%AE%E9%A2%98%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%BC%96%E8%AF%91%E5%99%A8%E4%B8%AD%EF%BC%8C%E5%88%99%E5%8F%AF%E4%BB%A5%E9%80%9A%E8%BF%87%E5%B0%86%EF%BC%83%EF%BC%81%5Bfeature%EF%BC%88bare_cell%EF%BC%89%5D%E6%B7%BB%E5%8A%A0%E5%88%B0%E9%A1%B9%E7%9B%AE%E7%9A%84%E4%B8%BB%E7%AE%A1%E4%B8%AD%E3%80%82%0A

%E8%BF%99%E6%98%AF%E4%B8%80%E4%B8%AA%E5%9C%A8%E7%A8%B3%E5%AE%9A%E7%BC%96%E8%AF%91%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%A2%9D%E5%A4%96%E7%9A%84%E4%BE%9D%E8%B5%96%E6%80%A7%EF%BC%9A

%0A

%0A

>%E5%9C%A8%E6%93%8D%E5%9C%BA%E4%B8%8A%E8%87%AA%E5%B7%B1%E5%B0%9D%E8%AF%95%EF%BC%81

%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
作者最新文章
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板