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

相关推荐
ai小鬼头2 小时前
Ollama+OpenWeb最新版0.42+0.3.35一键安装教程,轻松搞定AI模型部署
后端·架构·github
萧曵 丶3 小时前
Rust 所有权系统:深入浅出指南
开发语言·后端·rust
老任与码4 小时前
Spring AI Alibaba(1)——基本使用
java·人工智能·后端·springaialibaba
华子w9089258594 小时前
基于 SpringBoot+VueJS 的农产品研究报告管理系统设计与实现
vue.js·spring boot·后端
星辰离彬5 小时前
Java 与 MySQL 性能优化:Java应用中MySQL慢SQL诊断与优化实战
java·后端·sql·mysql·性能优化
GetcharZp6 小时前
彻底告别数据焦虑!这款开源神器 RustDesk,让你自建一个比向日葵、ToDesk 更安全的远程桌面
后端·rust
solohoho7 小时前
Rust:结构体、方法生命周期标注核心要义
rust
jack_yin7 小时前
Telegram DeepSeek Bot 管理平台 发布啦!
后端
小码编匠7 小时前
C# 上位机开发怎么学?给自动化工程师的建议
后端·c#·.net
库森学长7 小时前
面试官:发生OOM后,JVM还能运行吗?
jvm·后端·面试