6 releases

new 0.17.4 Apr 12, 2025
0.17.3 Apr 12, 2025
0.16.1 Apr 11, 2025

#5 in #maybe

Download history 142/week @ 2025-04-05

156 downloads per month

MIT license

25KB
317 lines

maybe-once

crates.io Build Status codecov

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