try_drop/drop_strategies/once_cell/
mod.rs

1//! Types and utilities for the once cell try drop strategy.
2mod thread_unsafe;
3pub use thread_unsafe::ThreadUnsafeOnceCellDropStrategy;
4mod private {
5    pub trait Sealed {}
6}
7
8use crate::{FallibleTryDropStrategy, TryDropStrategy};
9pub use once_cell::sync::OnceCell;
10use std::error::Error as StdError;
11use std::fmt;
12use std::marker::PhantomData;
13use std::sync::Arc;
14pub use thread_unsafe::*;
15
16/// Ignore the occupied error value and continue.
17#[cfg_attr(
18    feature = "derives",
19    derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)
20)]
21pub enum Ignore {}
22
23impl Mode for Ignore {}
24impl private::Sealed for Ignore {}
25
26/// Return an error with the underlying error value if the cell is occupied.
27#[cfg_attr(
28    feature = "derives",
29    derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)
30)]
31pub enum Error {}
32
33impl Mode for Error {}
34impl private::Sealed for Error {}
35
36/// How to handle cases where the error value is already occupied.
37pub trait Mode: private::Sealed {}
38
39/// An error which is returned if the cell is already occupied.
40#[derive(Debug)]
41pub struct AlreadyOccupiedError(pub anyhow::Error);
42
43impl StdError for AlreadyOccupiedError {
44    fn source(&self) -> Option<&(dyn StdError + 'static)> {
45        Some(self.0.as_ref())
46    }
47}
48
49impl fmt::Display for AlreadyOccupiedError {
50    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
51        f.write_str("an already existing error was occupied in this cell")
52    }
53}
54
55/// A try drop strategy which sets an error value once.
56//
57/// This try drop strategy can only handle single errors. If you want to handle multiple errors,
58/// see the [`BroadcastDropStrategy`].
59///
60/// The most common use case of this is when you want to get an error from inside a function which
61/// calls [`TryDrop`](crate::TryDrop).
62///
63/// # Examples
64/// ```ignore
65/// use once_cell::sync::OnceCell;
66/// use std::sync::Arc;
67/// use try_drop::drop_strategies::once_cell::Ignore;
68/// use try_drop::drop_strategies::OnceCellTryDropStrategy;
69///
70/// fn calls_try_drop(may_fail: ThisDropMayFail) {
71///     // do something with `may_fail`
72/// }
73///
74/// let error = Arc::new(OnceCell::new());
75/// let strategy = OnceCellTryDropStrategy::<Ignore>::new(Arc::clone(&error));
76/// let may_fail = ThisDropMayFail::new_with_strategy(strategy);
77///
78/// calls_try_drop(may_fail);
79///
80/// if let Some(error) = Arc::try_unwrap(error)
81///     .expect("arc still referenced by `calls_try_drop`")
82///     .take()
83/// {
84///     println!("an error occurred in `calls_try_drop`: {error}")
85/// }
86/// ```
87///
88/// [`BroadcastDropStrategy`]: crate::drop_strategies::BroadcastDropStrategy
89#[cfg_attr(feature = "derives", derive(Debug, Clone, Default))]
90pub struct OnceCellDropStrategy<M: Mode> {
91    /// The inner error value.
92    pub inner: Arc<OnceCell<anyhow::Error>>,
93    _mode: PhantomData<M>,
94}
95
96impl OnceCellDropStrategy<Ignore> {
97    /// Create a new once cell drop strategy which will ignore if there is already an error value in
98    /// its cell.
99    pub fn ignore(item: Arc<OnceCell<anyhow::Error>>) -> Self {
100        Self::new(item)
101    }
102}
103
104impl OnceCellDropStrategy<Error> {
105    /// Create a new once cell drop strategy which will error if there is already an error value in
106    /// its cell.
107    pub fn error(item: Arc<OnceCell<anyhow::Error>>) -> Self {
108        Self::new(item)
109    }
110}
111
112impl<M: Mode> OnceCellDropStrategy<M> {
113    /// Creates a new drop strategy which sets an error value once.
114    pub fn new(item: Arc<OnceCell<anyhow::Error>>) -> Self {
115        Self {
116            inner: item,
117            _mode: PhantomData,
118        }
119    }
120}
121
122impl TryDropStrategy for OnceCellDropStrategy<Ignore> {
123    fn handle_error(&self, error: anyhow::Error) {
124        let _ = self.inner.set(error);
125    }
126}
127
128impl FallibleTryDropStrategy for OnceCellDropStrategy<Error> {
129    type Error = AlreadyOccupiedError;
130
131    fn try_handle_error(&self, error: anyhow::Error) -> Result<(), Self::Error> {
132        self.inner.set(error).map_err(AlreadyOccupiedError)
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139    use crate::drop_strategies::PanicDropStrategy;
140    use crate::test_utils::{ErrorsOnDrop, Fallible};
141    use crate::PureTryDrop;
142
143    fn test<M: Mode>()
144    where
145        OnceCellDropStrategy<M>: FallibleTryDropStrategy,
146    {
147        let item = Arc::new(OnceCell::new());
148        let strategy = OnceCellDropStrategy::<M>::new(Arc::clone(&item));
149        let errors =
150            ErrorsOnDrop::<Fallible, _>::given(strategy, PanicDropStrategy::DEFAULT).adapt();
151        drop(errors);
152        Arc::try_unwrap(item)
153            .expect("item still referenced by `errors`")
154            .into_inner()
155            .expect("no error occupied in `OnceCellDropStrategy`");
156    }
157
158    #[test]
159    fn test_ignore() {
160        test::<Ignore>();
161    }
162
163    #[test]
164    fn test_error() {
165        test::<Error>();
166    }
167}