本文是一篇关于Rust全局变量的翻译文章。
2021 年 11 月,我写了一篇博文,研究了 Rust 与全局变量的奇怪关系。它旨在解释为什么这种无处不在的语言特性需要外部 crates,并以关于在新代码中使用全局变量的个人建议结束。两年过去了,Rust 已经发生了很大的变化,是时候重新审视一下了。本文的其余部分假定您已经阅读了上一篇文章或熟悉该主题。
Const Mutex 和 RwLock 构造函数
第一个变化是 Mutex::new()
从 Rust 1.63 开始是 const,所以上一篇文章中的这个例子现在可以按预期编译和工作:
// 2年前不能编译,现在可以了
static LOG_FILE: Mutex<String> = Mutex::new(String::new());
1.62 为这一改进奠定了基础,它在 Linux 上用轻量级、非分配实现取代了 Mutex、RwLock 和 CondVar,并且 1.63 扩展为在所有平台上提供这些类型的const构造。结果是,对于简单类型的互斥保护全局变量,无需任何特殊处理就能工作。
尽管我们不再需要将每个静态 Mutex
封装在 OnceCell
或等效物中,但我们仍然需要一个类似cell的包装器,用于仅在首次使用时完成锁定写入以初始化值的情况。在这种情况下,对全局的后续访问是只读的,不应该需要锁定,只需要原子检查。这是全局变量的一种非常常见的用法,一个很好的例子是全局变量持有一个延迟编译的正则表达式。
这给我们带来了下一个更重要的消息。
OnceCell 已进入标准库
从 Rust 1.70 开始,once_cell
crate 的 once_cell::sync::OnceCell
集成到标准库中,成为 std::sync::OnceLock
。在 Rust 存在以来,这是第一次,你不需要编写不安全的代码,也不需要引入封装它的外部 crate,就能够创建在首次使用时初始化的全局/静态变量。用法基本与once_cell
相同:
use std::sync::OnceLock;
use regex::Regex;
pub fn log_file_regex() -> &'static Regex {
static LOG_FILE_REGEX: OnceLock<Regex> = OnceLock::new();
LOG_FILE_REGEX.get_or_init(|| Regex::new(r#"^\d+-[[:xdigit:]]{8}$"#).unwrap())
}
// use log_file_regex().is_match(some_name) anywhere in your program
这个新增功能乍看起来可能并不像个大事件,因为once_cell
多年来一直提供了相同的功能。然而,将其加入到标准库中在几个方面极大地有益于该语言。首先,应用程序和库都广泛使用 initialize-on-first-use
的全局变量,现在两者都可以从它们的依赖项中淘汰像once_cell
和lazy_static
这样的 crate。其次,现在可以通过宏生成的代码创建全局变量,而不会出现笨拙的 once_cell
再导出和其他逻辑问题。第三,它使得教授这门语言变得更容易,教材不再需要决定是否涵盖once_cell
或lazy_static
,也不需要解释为什么一开始就需要外部 crate 来处理全局变量。这个煞费苦心的长篇 StackOverflow 回答就是一个深陷泥潭的好例子,我先前关于这个主题的博客文章也是如此。后者中的整个stdlib/unsafe部分现在已经变得过时,因为使用OnceLock
可以在不损失性能的情况下安全地实现相同的效果。
然而,工作还没有完成。请注意静态变量如何被放置在包含对OnceLock::get_or_init()
进行唯一调用的函数内部。这种模式确保对静态OnceLock
的每次访问都通过一个位置,该地方还对其初始化。once_cell
通过once_cell::sync::Lazy
减少了这种冗长性,但等效的stdlib类型尚未稳定,卡在一些技术问题上。将全局变量放置在函数内的解决方法并不是一个重大障碍,但值得一提。当比较OnceLock
的使用便捷性与lazy_static::lazy_static!
或once_cell::sync::Lazy
相比时,这一点尤为重要,后两者都提供了在单一位置初始化而无需额外工作的便利性。
2024年使用什么
两年前,我建议的TL;DR是“根据你更喜欢的语法选择使用once_cell
或lazy_static
”。现在,建议转变为:在几乎所有情况下使用标准库设施,比如OnceLock或原子操作,当你需要的便利性尚未被标准库覆盖时,再使用once_cell
。
特别是:
static
中使用的类型支持线程安全的内部可变性并具有const构造函数时,可以直接将其声明为静态。 (编译器会为你检查所有这些,只需查看它是否能编译。) 这以前仅包括原子类型,但现在还包括互斥锁和读写锁。 因此,如果像static CURRENT_CONFIG: Mutex<Option<Config>> = Mutex::new(None)
或static SHOULD_LOG: AtomicBool = AtomicBool::new(true)
对你有用,可以直接使用。std::sync::OnceLock
,最好封装在如上所示的函数中。once_cell::sync::Lazy
。该类型很可能以某种形式稳定下来,这使其优于lazy_static
。在新代码中使用lazy_static
没有好的理由。请注意,使用once_cell
或lazy_static
的现有代码并不需要立即处理。这些 crate 将无限期保持可用,并且它们生成的汇编代码几乎与标准库的OnceLock
相同。上述建议旨在指导你对新代码或你正在重构的代码的决策。
文章: https://morestina.net/blog/2055/rust-global-variables-two-years-on
minus 是一个用 Rust 编写的异步终端分页库。这个版本有很多新功能:
GitHub: https://github.com/arijit79/minus
项目作者是一名 CTF 玩家,在玩 CTF(夺旗)时需要大量的工具。大多数渗透测试 Linux 发行版不提供软件包,有些发行包很旧,例如 kali 或 parrot。
为了解决这个问题,作者写了一个包管理器 hysp,它有简单的 pkgs toml 配置,它允许二进制安装,目前使用 sha 进行二进制验证,(计划在下一个版本里程碑中使用 blake3)。
刚刚发布了一个新版本,其中包含定义自定义配置文件的选项,例如定义 home、bin、data directory 和 repository 以查找包。
GitHub: https://github.com/pwnwriter/hysp
From 日报小组 长琴
社区学习交流平台订阅: