>
在本文中,我将讨论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中文网其他相关文章!