devela/code/guard/mod.rs
1// devela::code::guard
2//
3//! defines the [`ScopeGuard`] struct.
4
5use crate::{Deref, DerefMut};
6
7/// A guard that executes a callback on drop, using an associated state.
8///
9#[doc = crate::doc_!(vendor: "stated-scope-guard")]
10pub struct ScopeGuard<T, F: FnOnce(T, &S), S> {
11 /// The guarded value,
12 /// wrapped in an Option to allow taking ownership during drop.
13 value: Option<T>,
14 /// A callback to process the value and state on drop,
15 /// wrapped in an Option for safe ownership transfer.
16 callback: Option<F>,
17 /// The associated state passed to the callback.
18 state: S,
19}
20
21impl<T> ScopeGuard<T, fn(T, &bool), bool> {
22 /// Constructs a scope guard with a boolean state (defaulting to true).
23 ///
24 /// The callback is executed on drop unless dismissed.
25 ///
26 /// # Example
27 /// ```
28 /// # use devela::{Cell, ScopeGuard};
29 /// let result = Cell::new(0);
30 /// {
31 /// let _guard = ScopeGuard::new(10, |value| {
32 /// result.set(value + 5);
33 /// });
34 /// }
35 /// assert_eq!(result.get(), 15);
36 /// ```
37 pub fn new<F: FnOnce(T)>(value: T, callback: F) -> ScopeGuard<T, impl FnOnce(T, &bool), bool> {
38 ScopeGuard::with(value, true, move |value, state: &bool| {
39 if *state {
40 callback(value);
41 }
42 })
43 }
44}
45impl<T, F: FnOnce(T, &bool)> ScopeGuard<T, F, bool> {
46 /// Dismisses the callback for a boolean state guard.
47 ///
48 /// Once dismissed, the callback won’t be executed on drop.
49 ///
50 /// # Example
51 /// ```
52 /// # use devela::{Cell, ScopeGuard};
53 /// let result = Cell::new(0);
54 /// {
55 /// let mut guard = ScopeGuard::new(10, |value| {
56 /// result.set(value + 5);
57 /// });
58 /// guard.dismiss();
59 /// }
60 /// assert_eq!(result.get(), 0);
61 /// ```
62 pub fn dismiss(&mut self) {
63 self.set_state(false);
64 }
65}
66impl<T, F: FnOnce(T, &S), S> ScopeGuard<T, F, S> {
67 /// Creates a scope guard with a custom state.
68 ///
69 /// The guarded value is accessible via `Deref` and `DerefMut`.
70 ///
71 /// # Example
72 /// ```
73 /// # use devela::{Cell, ScopeGuard};
74 /// // A simple resource that requires cleanup.
75 /// struct Resource;
76 /// impl Resource {
77 /// fn new() -> Self { Resource }
78 /// /// Cleans up the resource using the given strategy, updating the flag accordingly.
79 /// fn cleanup(&self, strategy: &Cleanup, flag: &Cell<&'static str>) {
80 /// match strategy {
81 /// Cleanup::Standard => flag.set("standard cleanup"),
82 /// Cleanup::Alternate => flag.set("alternate cleanup"),
83 /// }
84 /// }
85 /// }
86 /// // Define different cleanup strategies.
87 /// enum Cleanup {
88 /// Standard,
89 /// Alternate,
90 /// }
91 /// let cleanup_flag = Cell::new("not cleaned");
92 /// {
93 /// let mut guard = ScopeGuard::with(
94 /// Resource::new(),
95 /// Cleanup::Standard,
96 /// |res, strategy| { res.cleanup(strategy, &cleanup_flag) }
97 /// );
98 /// // Perform operations that require changing the cleanup strategy.
99 /// guard.set_state(Cleanup::Alternate);
100 /// } // When the guard goes out of scope, it triggers the cleanup callback.
101 /// assert_eq!(cleanup_flag.get(), "alternate cleanup");
102 /// ```
103 pub fn with(value: T, state: S, callback: F) -> Self {
104 Self {
105 value: Some(value),
106 state,
107 callback: Some(callback),
108 }
109 }
110 /// Updates the current state.
111 pub fn set_state(&mut self, state: S) {
112 self.state = state;
113 }
114}
115#[rustfmt::skip]
116impl<T, F: FnOnce(T, &S), S> Deref for ScopeGuard<T, F, S> {
117 type Target = T;
118 fn deref(&self) -> &Self::Target {
119 #[cfg(any(feature = "safe_code", not(feature = "unsafe_layout")))]
120 { self.value.as_ref().unwrap() }
121 #[cfg(all(not(feature = "safe_code"), feature = "unsafe_layout"))]
122 // SAFETY: `value` is always `Some` until dropped
123 unsafe { self.value.as_ref().unwrap_unchecked() }
124 }
125}
126#[rustfmt::skip]
127impl<T, F: FnOnce(T, &S), S> DerefMut for ScopeGuard<T, F, S> {
128 fn deref_mut(&mut self) -> &mut Self::Target {
129 #[cfg(any(feature = "safe_code", not(feature = "unsafe_layout")))]
130 { self.value.as_mut().unwrap() }
131 #[cfg(all(not(feature = "safe_code"), feature = "unsafe_layout"))]
132 // SAFETY: `value` is always `Some` until dropped
133 unsafe { self.value.as_mut().unwrap_unchecked() }
134 }
135}
136impl<T, F: FnOnce(T, &S), S> Drop for ScopeGuard<T, F, S> {
137 /// On drop, invokes the callback with the guarded value and a reference to the current state.
138 fn drop(&mut self) {
139 let (value, callback) = {
140 #[cfg(any(feature = "safe_code", not(feature = "unsafe_layout")))]
141 {
142 let value = self.value.take().unwrap();
143 let callback = self.callback.take().unwrap();
144 (value, callback)
145 }
146 #[cfg(all(not(feature = "safe_code"), feature = "unsafe_layout"))]
147 {
148 // SAFETY: `value` is always `Some` until dropped
149 let value = unsafe { self.value.take().unwrap_unchecked() };
150 // SAFETY: `callback` is always `Some` until dropped
151 let callback = unsafe { self.callback.take().unwrap_unchecked() };
152 (value, callback)
153 }
154 };
155 callback(value, &self.state);
156 }
157}