6 releases
new 0.17.4 | Apr 12, 2025 |
---|---|
0.17.3 | Apr 12, 2025 |
0.16.1 | Apr 11, 2025 |
#5 in #maybe
156 downloads per month
25KB
317 lines
maybe-once
What is this?
maybe-once
offers a variation of OnceLock
that keeps track of the number of references to the internal data
and drops it every time the references counter goes to 0.
Why is this useful?
In Rust static variables are not dropped when the program terminates. This is a problem when you need to initialize a shared resource that must be dropped when it is no longer used or when the process terminates. This happens, for example, when you a have a common resource to be used by a set of integration tests, and you want it to be dropped when the tests terminates.
Check the examples to see how to use it with docker and testcontainers.
Usage examples
mod test {
use std::sync::OnceLock;
use maybe_once::blocking::{Data, MaybeOnce};
/// A data initializer function. This can be called more than
/// once.
/// If everything goes as expected, it should only be called
/// once.
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 `MaybeOnce`
/// object and returns a `Data` object.
pub fn data(serial: bool) -> Data<'static, String> {
static DATA: OnceLock<MaybeOnce<String>> = OnceLock::new();
DATA.get_or_init(|| MaybeOnce::new(|| init()))
.data(serial)
}
/// Here we have multiple tests that access the data.
/// As the tests are executed in parallel in multiple threads,
/// the internal counter of the `MaybeOnce` object will be
/// incremented to 3.
/// It will then decrement every time a test finishes,
/// and incrementes each time a new test starts.
/// Once the internal counter goes to 0, the data will be
/// dropped.
/// At this points all tests are (statistically) complete,
/// but if for some reason the data is accessed again, it will
/// be recreated using the `init` function.
///
/// WARNING: If you execute the tests with a single thread,
/// the data will be dropped after each test and recreated
/// each time.
#[test]
fn test1() {
let data = data(false);
println!("{}", *data);
}
#[test]
fn test2() {
let data = data(false);
println!("{}", *data);
}
#[test]
fn test3() {
let data = data(false);
println!("{}", *data);
}
}
Usage with tokio
The tokio
feature of this crate allows you to use the optional MaybeOnceAsync
object to initialize a shared resource using an async function.
#[cfg(feature = "tokio")]
mod test {
use std::sync::OnceLock;
use maybe_once::tokio::{Data, MaybeOnceAsync};
/// A data async initializer function.
async fn init() -> String {
// Expensive initialization logic here.
"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
}
#[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);
}
}
Dependencies
~0.4–6MB
~32K SLoC