use_with/
lib.rs

1//! # use_with
2//!
3//! Provides resource management utilities, ensuring that resources are properly utilized
4//! and subsequently dropped, similar to patterns found in other programming languages like Kotlin's `use` function
5//! and C#'s `using` block.
6//!
7//! This module offers two primary functions:
8//! - `use_with`: Executes a closure synchronously, consuming the resource.
9//! - `use_with_async`: Executes an asynchronous closure, consuming the resource.
10//!
11//! These functions facilitate safe and efficient resource handling, ensuring that resources are properly utilized
12//! and dropped, even in asynchronous contexts.
13//!
14//! # Features
15//! - **Synchronous Resource Management:** The `use_with` function allows for synchronous operations on resources,
16//!   ensuring that resources are properly utilized and dropped after the operation completes.
17//!
18//! - **Asynchronous Resource Management:** The `use_with_async` function facilitates asynchronous operations on resources,
19//!   ensuring that resources are properly utilized and dropped after the asynchronous operation completes.
20//!
21//! # Usage
22//!To use these functions, the `Use` trait is auto-implemented for your resource types; simply call the appropriate method:
23//!
24//! ```rust
25//! use use_with::Use;
26//!
27//! struct Resource;
28//!
29//! impl Resource {
30//!     fn new() -> Self {
31//!         Resource
32//!     }
33//! }
34//!
35//! let result = Resource::new().use_with(|res| {
36//!     // Perform operations with `res`, return anything.
37//!     42
38//! });
39//!
40//! assert_eq!(result, 42);
41//! ```
42
43#![forbid(unsafe_code)]
44
45use std::future::Future;
46
47/// A trait that facilitates resource management by ensuring proper usage and subsequent dropping.
48///
49/// This trait provides two methods:
50/// - `use_with`: Executes a closure synchronously, consuming the resource.
51/// - `use_with_async`: Executes an asynchronous closure, consuming the resource.
52///
53/// Implementing this trait allows for safe and efficient resource handling, ensuring that resources
54/// are properly utilized and dropped, even in asynchronous contexts.
55pub trait Use {
56    /// Executes a closure synchronously, consuming the resource.
57    ///
58    /// This method takes ownership of `self` and applies the provided closure `f` to it.
59    /// After the closure executes, `self` is dropped.
60    ///
61    /// # Parameters
62    /// - `f`: A closure that takes ownership of `self` and returns a value of type `T`.
63    ///
64    /// # Returns
65    /// - A value of type `T`, which is the result of the closure `f`.
66    ///
67    /// # Examples
68    /// ```rust
69    /// use use_with::Use;
70    ///
71    /// struct Resource;
72    ///
73    /// impl Resource {
74    ///     fn new() -> Self {
75    ///         Resource
76    ///     }
77    /// }
78    ///
79    /// let result = Resource::new().use_with(|res| {
80    ///     // Perform operations with `res`, return anything.
81    ///     42
82    /// });
83    ///
84    /// assert_eq!(result, 42);
85    /// ```
86    fn use_with<U, F: FnOnce(Self) -> U>(self, f: F) -> U
87    where
88        Self: Sized,
89    {
90        f(self)
91    }
92
93    /// Executes an asynchronous closure, consuming the resource.
94    ///
95    /// This method takes ownership of `self` and applies the provided asynchronous closure `f` to it.
96    /// After the asynchronous operation completes, `self` is dropped.
97    ///
98    /// # Parameters
99    /// - `f`: An asynchronous closure that takes ownership of `self` and returns a future.
100    ///
101    /// # Returns
102    /// - A future that resolves to a value of type `U`, which is the result of the asynchronous operation.
103    ///
104    /// # Examples
105    /// ```rust
106    /// # #[tokio::main]
107    /// # async fn main() {
108    /// use use_with::Use;
109    /// use std::future::Future;
110    ///
111    /// struct Resource;
112    ///
113    /// impl Resource {
114    ///     fn new() -> Self {
115    ///         Resource
116    ///     }
117    /// }
118    ///
119    /// // Usage example
120    /// let future = Resource::new().use_with_async(|res| async {
121    ///     // Perform asynchronous operations with `res`, return anything.
122    ///     42
123    /// });
124    ///
125    /// // Await the result
126    /// assert_eq!(future.await, 42);
127    /// # }
128    /// ```
129    fn use_with_async<F, Fut, U>(self, f: F) -> impl Future<Output = U> + Send
130    where
131        Self: Sized + Send,
132        F: FnOnce(Self) -> Fut + Send,
133        Fut: Future<Output = U> + Send,
134    {
135        async { f(self).await }
136    }
137}
138
139impl<T> Use for T {}
140
141/// Executes a closure with a resource, ensuring the resource is properly utilized and dropped.
142///
143/// # Parameters
144/// - `resource`: The resource to be used.
145/// - `closure`: A closure that takes ownership of the resource and performs operations with it.
146///
147/// # Examples
148/// ```rust
149/// use use_with::using;
150///
151/// struct Resource(u32);
152///
153/// impl Resource {
154///     fn new(value: u32) -> Self {
155///         Resource(value)
156///     }
157/// }
158///
159/// let result = using!(Resource::new(10), it -> {
160///     it.0 + 32
161/// });
162/// assert_eq!(result, 42);
163///
164/// let resource = Resource::new(10);
165/// let result = using!(resource, value -> {
166///     value.0 + 32
167/// });
168/// assert_eq!(result, 42);
169/// ```
170///
171/// # Safety
172/// - The closure must not retain references to the resource beyond the scope of this function,
173///   as the resource will be dropped after the closure executes.
174#[macro_export]
175macro_rules! using {
176    ($resource:expr, $param:ident -> $body:block) => {{
177        let $param = $resource;
178        $body
179    }};
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185    use std::sync::{Arc, Mutex};
186
187    #[test]
188    fn test_resource_usage() {
189        let drop_flag = Arc::new(Mutex::new(false));
190
191        {
192            let drop_flag = drop_flag.clone();
193            struct TestResource(Arc<Mutex<bool>>);
194
195            impl Drop for TestResource {
196                fn drop(&mut self) {
197                    let mut flag = self.0.lock().unwrap();
198                    *flag = true;
199                    println!("TestResource is dropped!");
200                }
201            }
202
203            TestResource(drop_flag).use_with(|_res| {
204                println!("Using the resource");
205                // `_res` is consumed here
206            });
207        }
208
209        assert!(*drop_flag.lock().unwrap(), "Resource was not dropped");
210    }
211
212    #[test]
213    fn test_return_value() {
214        struct Resource;
215
216        impl Drop for Resource {
217            fn drop(&mut self) {
218                println!("Resource is dropped!");
219            }
220        }
221
222        let resource = Resource;
223        let result = resource.use_with(|_res| {
224            // Perform operations and return a value
225            42
226        });
227
228        assert_eq!(result, 42);
229        // Resource should be dropped after this point
230    }
231
232    #[test]
233    fn test_multiple_resources() {
234        struct Resource(&'static str);
235
236        impl Drop for Resource {
237            fn drop(&mut self) {
238                println!("{} is dropped!", self.0);
239            }
240        }
241
242        let res1 = Resource("Resource 1");
243        let res2 = Resource("Resource 2");
244
245        res1.use_with(|r1| {
246            println!("Using {}", r1.0);
247        });
248
249        res2.use_with(|r2| {
250            println!("Using {}", r2.0);
251        });
252
253        // Both resources should be dropped after this point
254    }
255
256    #[test]
257    fn test_nested_use_with() {
258        struct Resource(&'static str);
259
260        impl Drop for Resource {
261            fn drop(&mut self) {
262                println!("{} is dropped!", self.0);
263            }
264        }
265
266        let outer = Resource("Outer Resource");
267        let inner = Resource("Inner Resource");
268
269        outer.use_with(|o| {
270            println!("Using {}", o.0);
271            inner.use_with(|i| {
272                println!("Using {}", i.0);
273            });
274            // Inner resource should be dropped here
275        });
276        // Outer resource should be dropped after this point
277    }
278
279    #[test]
280    #[should_panic(expected = "Intentional panic")]
281    fn test_panic_in_use_with() {
282        struct Resource;
283
284        impl Drop for Resource {
285            fn drop(&mut self) {
286                println!("Resource is dropped!");
287            }
288        }
289
290        let resource = Resource;
291        resource.use_with(|_res| {
292            panic!("Intentional panic");
293        });
294
295        // Resource should be dropped even after a panic
296    }
297
298    #[test]
299    fn test_resource_modification() {
300        struct Resource {
301            value: i32,
302        }
303
304        impl Drop for Resource {
305            fn drop(&mut self) {
306                println!("Resource with value {} is dropped!", self.value);
307            }
308        }
309
310        let resource = Resource { value: 10 };
311        resource.use_with(|mut res| {
312            res.value += 5;
313            println!("Modified value: {}", res.value);
314            assert_eq!(res.value, 15);
315        });
316
317        // Resource should be dropped after this point
318    }
319
320    #[test]
321    fn test_resource_with_dependencies() {
322        struct Dependency;
323
324        impl Drop for Dependency {
325            fn drop(&mut self) {
326                println!("Dependency is dropped!");
327            }
328        }
329
330        #[allow(dead_code)]
331        struct Resource<'a> {
332            dep: &'a Dependency,
333        }
334
335        impl<'a> Drop for Resource<'a> {
336            fn drop(&mut self) {
337                println!("Resource is dropped!");
338            }
339        }
340
341        let dependency = Dependency;
342        let resource = Resource { dep: &dependency };
343        resource.use_with(|_res| {
344            println!("Using resource with dependency");
345            // `res` is consumed here
346        });
347
348        // Resource should be dropped after this point
349        // Dependency will be dropped afterward
350    }
351
352    #[test]
353    fn test_use_with_modifies_external_state() {
354        #[derive(Default)]
355        struct Resource;
356
357        // External state that we want to modify
358        let mut external_state = 0;
359
360        Resource::default().use_with(|_res| {
361            external_state += 1;
362        });
363
364        // Verify that the external state was modified
365        assert_eq!(external_state, 1);
366    }
367
368    #[tokio::test]
369    async fn test_use_with_async_modifies_external_state() {
370        #[derive(Default)]
371        struct Resource;
372
373        // Shared state wrapped in Arc<Mutex<...>>
374        let shared_state = Arc::new(tokio::sync::Mutex::new(0));
375
376        {
377            // Clone the Arc to increase the reference count
378            let state = Arc::clone(&shared_state);
379
380            // Create a new Resource and use `use_with_async` to pass an async closure
381            Resource::default()
382                .use_with_async(|_res| async move {
383                    let mut num = state.lock().await;
384                    *num += 1;
385                    println!("Shared state incremented: {}", *num);
386                    // Simulate asynchronous work
387                    tokio::time::sleep(std::time::Duration::from_millis(100)).await;
388                })
389                .await;
390            // `_res` is dropped here
391        }
392
393        // Verify that the shared state was modified
394        assert_eq!(*shared_state.lock().await, 1);
395    }
396}