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,当然这又是一个方案上的取舍了:)

相关推荐
程序员Agions1 分钟前
程序员邪修手册:那些不能写进文档的骚操作
前端·后端·代码规范
肌肉娃子5 分钟前
20260109.反思一个历史的编程的结构问题-更新频率不一致的数据不要放在同一个表
后端
凌览1 小时前
2026年1月编程语言排行榜|C#拿下年度语言,Python稳居第一
前端·后端·程序员
码事漫谈1 小时前
【深度解析】为什么C++有了malloc,还需要new?
后端
晴虹1 小时前
lecen:一个更好的开源可视化系统搭建项目--组件和功能按钮的权限控制--全低代码|所见即所得|利用可视化设计器构建你的应用系统-做一
前端·后端·低代码
Java编程爱好者1 小时前
Java 并发编程:JUC 包中原子操作类的原理和用法
后端
爱分享的鱼鱼1 小时前
Pinia 深度解析:现代Vue应用状态管理最佳实践
前端·后端
JOEH601 小时前
🚀 别再用 Future.get() 傻等了!CompletableFuture 异步编排实战,性能提升 300%!
后端·程序员
神奇小汤圆1 小时前
原来可以搭建一个HTTP服务
后端
Hooray111 小时前
前后端分离_案例学习_Python+Flask+VUE3
后端·python·学习·flask