Rust-开发应用-如何实现单例

前言

单例模式是一种创建型设计模式,其目的是确保一个类仅有一个实例,并且为该实例提供一个全局访问点。在实际工程中,我们会大量复用这种模式,用于配置表、中间件链接等场景。本文将说明如何在Rust中实现单例。

场景

我们用如下测试用例模拟实际工程中的两个测试用例,为了方便调试我们需要在用例中初始化tracing_subscriber才能记录程序的日志。

rust 复制代码
#[cfg(test)]
mod tests {
    #[tokio::test]
    async fn test_1() {
        tracing_subscriber::fmt::init();
        tracing::info!("Running test 1");
        assert_eq!(2 + 2, 4);
        tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
        tracing::info!("Test 1 completed");
    }

    #[tokio::test]
    async fn test_2() {
        tracing_subscriber::fmt::init();
        tracing::info!("Running test 2");
        assert_eq!(3 + 3, 6);
        tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
        tracing::info!("Test 2 completed");
    }
}

运行测试,你会发现报如下错误:

shell 复制代码
Unable to install global subscriber: SetGlobalDefaultError("a global default trace dispatcher has already been set")

原因是我们在两个测试用例当中都通过tracing_subscriber::fmt::init()设置了一个全局的default trace dispatcher。我们将通过单例来解决这个问题,需注意本文解法仅是为了演示如何实现单例,在实际工程实践中你则可以考虑通过test_log等方式解决:)

lazy_static

在Rust@1.70之前,我们通常会使用lazy_static来解决这个问题。lazy_static是用于在Rust中声明延迟求值静态变量的宏。使用它,可以拥有需要在运行时执行代码才能初始化的静态变量,也就是懒加载的能力。使用lazy_static你需要在你的项目中添加依赖:

toml 复制代码
[dependencies]
lazy_static = "1.5.0"

我们的代码如下:

rust 复制代码
#[cfg(test)]
mod tests {
    lazy_static::lazy_static! {
        static ref G_TRACING: std::sync::Arc<()> = std::sync::Arc::new({
            tracing_subscriber::fmt::init();
        });
    }

    #[tokio::test]
    async fn test_1() {
        let _tracing = G_TRACING.clone();
        tracing::info!("Running test 1");
        assert_eq!(2 + 2, 4);
        tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
        tracing::info!("Test 1 completed");
    }

    #[tokio::test]
    async fn test_2() {
        let _tracing = G_TRACING.clone();
        tracing::info!("Running test 2");
        assert_eq!(3 + 3, 6);
        tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
        tracing::info!("Test 2 completed");
    }
}

使用Arc使我们创建的静态变量能够被clone。使用G_TRACING我们就可以保证该方法仅会在第一次执行时被运行。

std

如果我们现在查看lazy_static的crates.io,那么会发现其主页有这样一段描述:

It is now possible to easily replicate this crate's functionality in Rust's standard library with std::sync::OnceLock.

当前开发者已经可以通过使用Rust标准库中的std::sync::OnceLock来复用lazy_static的功能(准确说应该是在Rust@1.70版本之后)。

我们可以通过以下代码来实现上文中的功能:

rust 复制代码
#[cfg(test)]
mod tests {
    static G_TRACING: std::sync::OnceLock<()> = std::sync::OnceLock::new();

    fn g_tracing() -> &'static () {
        G_TRACING.get_or_init(|| {
            tracing_subscriber::fmt::init();
        })
    }

    #[tokio::test]
    async fn test_1() {
        g_tracing();
        tracing::info!("Running test 1");
        assert_eq!(2 + 2, 4);
        tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
        tracing::info!("Test 1 completed");
    }

    #[tokio::test]
    async fn test_2() {
        g_tracing();
        tracing::info!("Running test 2");
        assert_eq!(3 + 3, 6);
        tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
        tracing::info!("Test 2 completed");
    }
}

官方文档介绍中,std::sync::OnceLock是线程安全版的std::cell::OnceCell,因此如果不需要考虑线程安全,你也可以使用OnceCell来实现你想要的功能。

最终运行结果:

bash 复制代码
running 2 tests
2025-07-07T12:21:31.703227Z  INFO learn_rust::tests: Running test 2
2025-07-07T12:21:31.703226Z  INFO learn_rust::tests: Running test 1
2025-07-07T12:21:41.709084Z  INFO learn_rust::tests: Test 1 completed
2025-07-07T12:21:41.709084Z  INFO learn_rust::tests: Test 2 completed
test tests::test_1 ... ok
test tests::test_2 ... ok

successes:

successes:
    tests::test_1
    tests::test_2

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 10.01s

附上项目的cargo.toml:

toml 复制代码
[package]
name = "learn_rust"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
tokio = { version = "1.0", features = ["full"] }
tracing = "0.1"
tracing-subscriber = "0.3"
lazy_static = "1.4"

[dev-dependencies]

总结

本文介绍了如何在Rust中实现单例;一般来说,在新项目中你完全可以使用标准库提供的能力。但考虑到一些历史项目需考虑版本兼容性,你可能仍然需要使用lazy_static,当然这又是一个方案上的取舍了:)

相关推荐
_風箏10 分钟前
Java【代码 15】文件操作相关方法(获取文件、复制文件、创建文件夹、获取图片文件、写出数据到文件、清理文件夹)
后端
Bohemian23 分钟前
实现一个单机版令牌桶限流器(字节)
后端·面试
风一样的树懒26 分钟前
ES是如何实现Master选举的?
后端
00后程序员26 分钟前
移动端 WebView 调试实战,多平台行为差异排查与统一调试流程
后端
Hejjon1 小时前
携带参数的表单文件上传 axios, SpringBoot
java·spring boot·后端
sivdead1 小时前
从被动查询到主动智能:数据应用智能体的技术演进路线图
人工智能·后端·架构
阿宙ppppp1 小时前
基于yolov5+LPRNet+flask+vue的车牌识别(2)
后端·图像识别
荔枝爱编程1 小时前
高性能企业级消息中心架构实现与分享(三):数据存储设计与高可用保障
spring boot·后端·消息队列
钟离墨笺1 小时前
Go 语言-->指针
开发语言·后端·golang