pub struct MaybeOnceAsync<T> { /* private fields */ }Expand description
A MaybeOnce object is a variation of the OnceLock object that keeps track of the number of references to the internal data
and drops it every time the references counter goes to 0. When the data is accessed,
it will be created if it does not exist, or it will recreated if it was previously dropped.
This object is to be used to inizialize a shared resource that must be dropped when it is no longer used or when the process terminates. This mechanism is used as a workaround for the fact that rust does not drop static items at the end of the program.
A typical example is to initialize an expensive object, for example to start a docker container, to be used by a set of integration tests, with the guarantee that it is properly dropped when the tests terminates.
Please note that, if this object is used in a single thread context, then it will drop the data after each access. This is caused by the fact that the internal reference counter will always be 0 once the data is dropped.
Example:
mod test {
use std::sync::OnceLock;
use maybe_once::tokio::{Data, MaybeOnceAsync};
/// A data initializer function. This can be called more than once.
/// If everything goes as expected, it should only be called once.
async fn init() -> String {
// Expensive initialization logic here.
// For example, you can start here a docker container (e.g. by using testcontainers),
// when there will be no more references to the data,
// the data will be dropped and the container will be stopped.
"hello".to_string()
}
/// A function that holds a static reference to the `MaybeOnceAsync` object
/// and returns a `Data` object.
pub async fn data(serial: bool) -> Data<'static, String> {
static DATA: OnceLock<MaybeOnceAsync<String>> = OnceLock::new();
DATA.get_or_init(|| MaybeOnceAsync::new(|| Box::pin(init())))
.data(serial)
.await
}
/// This test, and all the others, uses the data function to access the shared data.
/// The same data instance is shared between all the threads exactly like OnceLock does,
/// but when the all tests finish, the data will be dropped before the process terminates.
#[tokio::test]
async fn test1() {
let data = data(false).await;
println!("{}", *data);
}
#[tokio::test]
async fn test2() {
let data = data(false).await;
println!("{}", *data);
}
#[tokio::test]
async fn test3() {
let data = data(false).await;
println!("{}", *data);
}
}Implementations§
Source§impl<T> MaybeOnceAsync<T>
impl<T> MaybeOnceAsync<T>
Sourcepub fn new(init: fn() -> Pin<Box<dyn Send + Future<Output = T>>>) -> Self
pub fn new(init: fn() -> Pin<Box<dyn Send + Future<Output = T>>>) -> Self
Creates a new MaybeOnceAsync object with the given init function.
init is a function that creates a new T object. It is lazily called the first time
data is called and every time after the data is dropped.
The returned MaybeOnceAsync object is then used to access the shared data with the
data method.
Sourcepub async fn data(&self, serial: bool) -> Data<'_, T>
pub async fn data(&self, serial: bool) -> Data<'_, T>
This function returns a Data object, which allows you to access the shared data.
The serial parameter allows you to control whether the data is accessed in a serial
or parallel manner. If serial is true, the data will be accessed in a serial manner,
meaning that no other thread can access the data until the returned Data is dropped.
If serial is false, the data will be accessed in a parallel manner, meaning that
any number of threads can access the data at the same time.
The returned Data object implements Deref and AsRef, so you can use it like a reference.
The Data object also implements Drop, so when it goes out of scope, the lock is released.