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}