前言
为什么需要用到单例模式?
因为在平时开发过程中,很多模块我们只需要初始化一次。 例如连接池,各种 client等。
本文就以连接池为例,探索 单例模式在 Rust 中的实现。
- lazy_static!
- unsafe
- Once 闭包
lazy_static!
这个库想必大家都知道,用于延迟计算静态变量。
那么基于这个特性我们很轻松就可以实现单例模式。
添加 cargo 依赖
lazy_static = "1.4.0"
示例代码
#[cfg(test)]
mod test {
use std::sync::{Arc, Mutex};
use lazy_static::lazy_static;
struct Singleton;
lazy_static! {
static ref INS: Arc<Mutex<Singleton>> = Arc::new(Mutex::new(Singleton {}));
}
impl Singleton {
pub fn get_instance() -> Arc<Mutex<Singleton>> {
INS.clone()
}
}
#[test]
pub fn test_singleton() {
let ins1 = Singleton::get_instance();
let ins2 = Singleton::get_instance();
assert_eq!(Arc::ptr_eq(&ins1, &ins2), true);
}
}
running 1 test
test test::test_singleton ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 17 filtered out; finished in 0.00s
unsafe 实现
这种解决方案比较暴力,大家都知道 Rust中,静态全局变量是不可变的。如果我们需要改变它就需要使用 unsafe
。 这时候就有小伙伴要说了,不是说 unsafe 不安全吗。
理论上 确实是这样,但是并不是说 使用 unsafe 就一定不安全。 只要我们合理的遵循 Rust心智模型,即便是用 unsafe 也能写出 安全的代码。
use std::sync::{Arc, Mutex};
use crate::{config::Config, global::CFG};
#[derive(Clone)]
pub struct Provider {
config: Arc<Config>,
redis_pool: deadpool_redis::Pool,
lapin_pool: deadpool_lapin::Pool,
}
impl Provider {
pub fn get_instance() -> Arc<Mutex<Provider>> {
static mut POINTER: Option<Arc<Mutex<Provider>>> = None;
unsafe {
POINTER
.get_or_insert_with(|| {
let cfg = CFG.clone();
Arc::new(Mutex::new(Provider::new(cfg)))
})
.clone()
}
}
pub fn new(config: Arc<Config>) -> Self {
let redis_pool = {
let cfg = deadpool_redis::Config::from_url(config.redis.dsn.clone());
let pool = cfg
.create_pool(Some(deadpool_redis::Runtime::Tokio1))
.unwrap();
pool
};
let lapin_pool = {
let mut cfg = deadpool_lapin::Config::default();
let url = config.lapin.dsn.clone();
cfg.url = Some(url.into());
let pool = cfg
.create_pool(Some(deadpool_lapin::Runtime::Tokio1))
.unwrap();
pool
};
Self {
config: config,
redis_pool: redis_pool,
lapin_pool: lapin_pool,
}
}
pub async fn lapin_conn(&self) -> Result<deadpool_lapin::Object, deadpool_lapin::PoolError> {
self.lapin_pool.get().await
}
pub async fn redis_conn(
&self,
) -> Result<deadpool_redis::Connection, deadpool_redis::PoolError> {
self.redis_pool.get().await
}
}
#[cfg(test)]
mod test {
use super::Provider;
#[tokio::test]
pub async fn test() {
let ins = Provider::get_instance();
let conn = ins.lock().unwrap().redis_conn().await;
}
}
once 闭包
这种方案和上面类似,只是用了 once 只执行一次的特性,这里就不详细概述了。