由Yashodhan Joshi写✏️
确定应用程序一开始就需要初始化每个部分吗?
在应用程序启动时,最常见的事情之一是初始化各种资源,如应用配置、日志服务等。这可以是应用配置、日志服务,或者是将在稍后由请求处理器使用的数据库连接。然而,并非所有这些资源都需要在应用程序刚启动时立即准备好,这可能会导致启动变慢。
这里就是懒惰初始化帮助我们将资源的初始化推迟到真正需要资源时才进行。如果某个资源完全没有被使用,初始化步骤就可以直接省略。
在早期版本中,Rust 的标准库不支持此类懒惰初始化。生态系统中有几个流行 crate 经常用于此类功能,例如 [lazy_static](https://blog.logrocket.com/rust-lazy-static-pattern/)
等等,还有 once_cell
。从 Rust 1.80 版本开始,这些 crate 提供的许多功能现在已在标准库中实现,可以替代这两个 crate。
在这篇帖子中,我们会看到:
- 懒加载模式
lazy_static
和once_cell
如何实现懒加载- 使用标准库实现懒加载
- 标准库、
lazy_static
和once_cell
的比较
让我们看一个例子,其中我们有一个不常使用的 API 端点,需要从磁盘读取并解析一个较大的文件。
我们可以在这个应用程序启动时进行读取和解析,然而,这会使得服务器在解析完成之前无法响应请求。也可能特定的 API 端点根本没有被调用,那么用于加载文件的资源就变得毫无用处。
另一个例子是,应用程序可能使用如 SQLite 或 Redis 这样的内存数据库。然而,但并非每次调用都需要数据库。因此,每次加载数据库到内存并保持连接是不必要的。
我们可以将这些资源的初始化延迟到真正需要时,在第一次使用时才进行初始化,并在之后需要时继续使用。这种模式叫作懒加载。
然而这在 Rust 中带来了一个小麻烦,我们必须要么将延迟初始化的资源作为参数传递给每个函数,要么将其作为静态全局变量,使用不安全的 Rust 进行运行时初始化。
为了防止这种情况,库如 lazy_static
或 once_cell
提供了安全的包装器来包装不安全的操作,我们就可以在代码中安全地使用这些库来初始化懒加载的值。
lazy_static
和 once_cell
如何实现延迟初始化
lazy_static
提供了一个宏来编写静态变量的初始化代码,这样,并在程序首次使用该变量时进行初始化。一般的语法是:如下:
use lazy_static::lazy_static;
lazy_static! {
static ref VAR: TYPE = {初始化代码};
// 这里定义了一个静态变量VAR,类型为TYPE,初始化代码为{初始化代码}。
}
全屏模式;退出全屏
例如,可以将日志级别设置为静态常量,可以这样做:
private static final int LOG_LEVEL = 1;
。
使用 `lazy_static` 宏来初始化一个静态变量 `LOG_LEVEL`,该变量用于存储日志级别。
```rust
use lazy_static::lazy_static;
lazy_static! {
static ref LOG_LEVEL: String = get_log_level();
}
fn get_log_level() -> String {
match std::env::var("LOG_LEVEL") {
Ok(s) => s,
Err(_) => "WARN".to_string(),
}
}
fn main() {
println!("{}", *LOG_LEVEL);
}
在这里,`lazy_static` 宏被用来定义一个静态变量 `LOG_LEVEL`,类型为 `String`,其值由 `get_log_level` 函数获取。`get_log_level` 函数会尝试从环境变量 `LOG_LEVEL` 获取日志级别,如果环境变量不存在,则默认设置为 "WARN"。
主函数 `main` 会打印出 `LOG_LEVEL` 的值到控制台。
全屏模式下按退出键退出
在 `lazy_static!` 定义中,代码使用 `get_log_level` 函数在运行时通过该函数来设置日志级别。
虽然很简单,这个实现也有一些自己的特点。我们得用 `static ref`,这不是有效的 Rust 语法,并且我们需要用 `LOG_LEVEL` 的解引用形式,就像在 `println!` 语句中那样。我们也可以用 `once_cell` crate 这样做:
use once_cell::sync::OnceCell;
static LOG_LEVEL: OnceCell<String> = OnceCell::new();
fn get_log_level() -> String {
match std::env::var("LOG_LEVEL") {
Ok(s) => s,
Err(_) => "WARN".to_string(),
}
}
fn main() {
let log_level = LOG_LEVEL.get_or_init(get_log_level);
println!("{}", log_level);
}
进入全屏模式 关闭全屏模式
在这里面,我们不再声明中指定代码,而当我们想检索值时,使用 `get_or_init` 方法来获取值。
如果值未被初始化,则会使用指定的函数来初始化值,否则将返回现有值。因为我们可以直接获取其值,因此不需要额外的解引用步骤。在初始化过程中,我们不需要任何额外的操作。
不过共同的一点是,你需要在依赖包中增加一个额外的外部crate。现在标准库提供了用于惰性模式的类型,因此我们可以直接使用它们,从而减少依赖项。
## 使用标准库进行懒加载
从 Rust 1.70 到 1.80,类似于 `lazy_static` 和 `once_cell` 的类型已经在 Rust 标准库中稳定下来。我们可以用它们来替代任何外部 crate,来实现类似的功能。
标准库中的`OnceLock`类型可以像`once_cell` crate中的`OnceCell`类型一样使用。
use std::sync::OnceLock;
static LOG_LEVEL: OnceLock<String> = OnceLock::new();
// 获取日志级别,如果环境变量中存在 LOG_LEVEL,则使用其值,否则使用 "WARN" 作为默认值。
fn get_log_level() -> String {
match std::env::var("LOG_LEVEL") {
Ok(s) => s,
Err(_) => "WARN".to_string(),
}
}
fn main() {
// 获取或初始化日志级别
let log_level = LOG_LEVEL.get_or_init(get_log_level);
println!("{}", log_level);
}
全屏模式,退出全屏
与 `once_cell` 的例子相比,我们将 `OnceCell` 替换为 `OnceLock`,不过其余的代码保持不变。`OnceLock` 类型也提供了与 `OnceCell` 中的 `get_or_init` 方法相同的功能。
就像 `lazy_static` 一样,我们也可以在声明中直接使用 `LazyLock` 来定义初始化函数,而不用宏。
使用std::sync::LazyLock; //这是一个同步原语,确保线程安全。
//定义一个静态变量LOG_LEVEL,类型为LazyLock<String>,其初始值通过调用get_log_level函数获取。
static LOG_LEVEL: LazyLock<String> = LazyLock::new(get_log_level);
//定义一个函数get_log_level,返回类型为String,该函数用于获取环境变量LOG_LEVEL的值,如果没有设置则返回默认值'WARN'.
fn get_log_level() -> String {
//通过match语法来匹配环境变量LOG_LEVEL的值,如果获取成功,返回成功获取到的字符串s,如果获取失败,返回默认字符串'WARN'.
match std::env::var("LOG_LEVEL") {
Ok(s) => s,
Err(_) => "WARN".to_string(),
}
}
//在主函数main中,该函数用于打印LOG_LEVEL的值到控制台。
fn main() {
println!("{}", *LOG_LEVEL); //打印LOG_LEVEL的值到控制台。
}
全屏模式退出全屏
我们在这里将初始化函数传递给 `LazyLock` 的初始化方法,当变量的值首次被访问时,内部会调用此函数来初始化值。
## 比较 `懒加载`, `一次单元` 和原生类型
如 Rust [1.70](https://blog.rust-lang.org/2023/06/01/Rust-1.70.0.html) 和 [1.80](https://blog.rust-lang.org/2024/07/25/Rust-1.80.0.html) 的发行说明所述,新添加的原生类型实现的代码借鉴自 `once_cell` crate。因此,这些新类型的功能与 `once_cell` crate 中的原始实现类似。
`lazy_static` 这个库使用自己的宏语法来声明静态变量,这点我偶尔还会忘记。相比之下,`once_cell` 不需要任何宏或自定义语法,仅依靠类型来实现懒惰处理。
在使用 `lazy_static` 时,初始化代码必须直接写在声明宏里。如果你需要更灵活的初始化方式,类似于 `get_or_init` 这样的函数,你必须使用 `once_cell` 或者标准库中的类型。
与这两个crate相比,内置类型的一个大优势是不需要任何额外的依赖。虽然这两个crate本身依赖较少,但这仍然意味着你的项目会多出几个依赖,并且编译时间也会稍微增加一些。这对你的项目来说意味着编译时间会稍微增加一些。
另一个优势在于原生类型的优势在于,它们由官方 Rust 标准库团队直接开发并维护。
## 结论如下:
这使得开发者可以直接在代码中使用懒加载,而无需额外添加依赖项。
通过这些类型,我们可以在需要时执行昂贵的计算,并且只需初始化正则表达式等昂贵构造一次,无需每次手动检查初始化。
话说,现有的`lazy_static`或`once_cell`等库仍然很受欢迎并且维护得很好,短期内它们不会被淘汰。
如果你已经在用它们,或者因为熟悉想继续用它们,你可以继续用它们而不用担心。即使你把代码换成原生类型,有些依赖可能还没换成原生类型,仍然在用这些crate,这样这些crate还会出现在你的依赖里。
不过,随着时间的推移,我们会更多地在之后创建的新项目中使用本地类型,而不是使用外部 crate 仓库中的 crate。
你可以在这里的[Github仓库](https://github.com/YJDoc2/LogRocket-Blog-Code/tree/main/native-lazy-types)查看代码示例。感谢您的阅读!
* * *
## LogRocket:全方位监控 Rust 应用的前端
调试 Rust 应用程序可能会很棘手,尤其是在用户遇到难以复现的问题时。如果您想监控和跟踪应用程序性能,自动显示错误,以及跟踪缓慢的网络请求和加载时间,可以试试 LogRocket([试用 LogRocket](https://lp.logrocket.com/blg/rust-signup?utm_source=devto&utm_medium=organic&utm_campaign=24Q4&utm_content=how-use-lazy-initialization-pattern-rust-1-80))。
[](https://lp.logrocket.com/blg/rust-signup?utm_source=devto&utm_medium=organic&utm_campaign=24Q4&utm_content=Rust注册页面)
[LogRocket](https://lp.logrocket.com/blg/rust-signup?utm_source=devto&utm_medium=organic&utm_campaign=24Q4&utm_content=如何在 Rust 中使用惰性初始化模式-1-80),它就像是网络应用的 DVR,记录了你的 Rust 应用中发生的每一件事。而不是猜测问题的原因,你可以汇集并报告在问题发生时应用的状态。LogRocket 还会监控你的应用性能,报告诸如客户端 CPU 负载、内存使用等指标。
让你的 Rust 应用调试更加现代化, [开始免费监控](https://lp.logrocket.com/blg/rust-signup?utm_source=devto&utm_medium=organic&utm_campaign=24Q4&utm_content=how-use-lazy-initialization-pattern-rust-1-80)。
共同學習,寫下你的評論
評論加載中...
作者其他優質文章