thread_guard/
lib.rs

1#![doc = include_str!("../README.md")]
2#![warn(missing_docs, missing_debug_implementations, unreachable_pub)]
3#![forbid(unsafe_code)]
4
5use std::any::Any;
6use std::fmt;
7use std::mem;
8use std::thread::{JoinHandle, Result as ThreadResult};
9
10/// A thread guard.
11///
12/// A thread guard that automatically joins the thread in the destructor.
13///
14/// Additionally, custom pre-actions and post-actions can be defined to execute
15/// before and after thread joining, respectively. The thread can also be
16/// explicitly joined using the `join` method. In this case, the pre-action is
17/// executed before the join, and the thread result is returned to the caller.
18pub struct ThreadGuard<T>(
19    #[allow(clippy::type_complexity)]
20    Option<(
21        JoinHandle<T>,
22        Box<dyn FnOnce(bool, JoinHandle<T>) -> ThreadResult<T> + Send>,
23    )>,
24);
25
26impl<T> ThreadGuard<T> {
27    /// Creates a new `ThreadGuard`.
28    pub fn new(handle: JoinHandle<T>) -> Self {
29        let action =
30            Box::new(move |_run_post_action, join_handle: JoinHandle<T>| join_handle.join());
31
32        Self(Some((handle, action)))
33    }
34
35    /// Creates a new `ThreadGuard` with the specified pre-action.
36    pub fn with_pre_action<U, F>(handle: JoinHandle<T>, pre_action: F) -> Self
37    where
38        for<'a> F: FnOnce(&JoinHandle<T>) -> U + Send + 'a,
39    {
40        Self::with_actions(handle, pre_action, |_, _| {})
41    }
42
43    /// Creates a new `ThreadGuard` with the specified post-action.
44    pub fn with_post_action<F>(handle: JoinHandle<T>, post_action: F) -> Self
45    where
46        for<'a> F: FnOnce(ThreadResult<T>) + Send + 'a,
47    {
48        Self::with_actions(handle, |_| {}, |_, result| post_action(result))
49    }
50
51    /// Creates a new `ThreadGuard` with the specified pre-action and
52    /// post-action.
53    pub fn with_actions<U, F, G>(handle: JoinHandle<T>, pre_action: F, post_action: G) -> Self
54    where
55        for<'a> F: FnOnce(&JoinHandle<T>) -> U + Send + 'a,
56        for<'a> G: FnOnce(U, ThreadResult<T>) + Send + 'a,
57    {
58        let action = Box::new(move |run_post_action, join_handle| {
59            let arg = pre_action(&join_handle);
60            let result = join_handle.join();
61            if run_post_action {
62                post_action(arg, result);
63
64                // This is a bit ugly. Another possibility would be for `action`
65                // to return an `Option<ThreadResult<T>>` but then we will have
66                // yet another infallible `unwrap`.
67                return Err(Box::new(()) as Box<dyn Any + Send>);
68            }
69
70            result
71        });
72
73        Self(Some((handle, action)))
74    }
75
76    /// Joins the guarded thread.
77    pub fn join(mut self) -> ThreadResult<T> {
78        let (handle, action) = self.0.take().unwrap();
79        mem::forget(self);
80
81        action(false, handle)
82    }
83}
84
85impl<T> Drop for ThreadGuard<T> {
86    fn drop(&mut self) {
87        let (handle, action) = self.0.take().unwrap();
88        let _ = action(true, handle);
89    }
90}
91
92impl<T> fmt::Debug for ThreadGuard<T> {
93    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
94        f.debug_struct("ThreadGuard").finish_non_exhaustive()
95    }
96}