Messages in this thread |  | | From | Wedson Almeida Filho <> | Date | Sun, 22 Jan 2023 03:31:12 -0300 | Subject | Re: [PATCH 1/5] rust: types: introduce `ScopeGuard` |
| |
On Fri, 20 Jan 2023 at 03:24, Boqun Feng <boqun.feng@gmail.com> wrote: > > On Thu, Jan 19, 2023 at 02:40:32PM -0300, Wedson Almeida Filho wrote: > > This allows us to run some code when the guard is dropped (e.g., > > implicitly when it goes out of scope). We can also prevent the > > guard from running by calling its `dismiss()` method. > > > > Signed-off-by: Wedson Almeida Filho <wedsonaf@gmail.com> > > --- > > rust/kernel/types.rs | 127 ++++++++++++++++++++++++++++++++++++++++++- > > 1 file changed, 126 insertions(+), 1 deletion(-) > > > > diff --git a/rust/kernel/types.rs b/rust/kernel/types.rs > > index e84e51ec9716..f0ad4472292d 100644 > > --- a/rust/kernel/types.rs > > +++ b/rust/kernel/types.rs > > @@ -2,7 +2,132 @@ > > > > //! Kernel types. > > > > -use core::{cell::UnsafeCell, mem::MaybeUninit}; > > +use alloc::boxed::Box; > > +use core::{ > > + cell::UnsafeCell, > > + mem::MaybeUninit, > > + ops::{Deref, DerefMut}, > > +}; > > + > > +/// Runs a cleanup function/closure when dropped. > > +/// > > +/// The [`ScopeGuard::dismiss`] function prevents the cleanup function from running. > > +/// > > +/// # Examples > > +/// > > +/// In the example below, we have multiple exit paths and we want to log regardless of which one is > > +/// taken: > > +/// ``` > > +/// # use kernel::ScopeGuard; > > +/// fn example1(arg: bool) { > > +/// let _log = ScopeGuard::new(|| pr_info!("example1 completed\n")); > > +/// > > +/// if arg { > > +/// return; > > +/// } > > +/// > > +/// pr_info!("Do something...\n"); > > +/// } > > +/// > > +/// # example1(false); > > +/// # example1(true); > > +/// ``` > > +/// > > +/// In the example below, we want to log the same message on all early exits but a different one on > > +/// the main exit path: > > +/// ``` > > +/// # use kernel::ScopeGuard; > > +/// fn example2(arg: bool) { > > +/// let log = ScopeGuard::new(|| pr_info!("example2 returned early\n")); > > +/// > > +/// if arg { > > +/// return; > > +/// } > > +/// > > +/// // (Other early returns...) > > +/// > > +/// log.dismiss(); > > +/// pr_info!("example2 no early return\n"); > > +/// } > > +/// > > +/// # example2(false); > > +/// # example2(true); > > +/// ``` > > +/// > > +/// In the example below, we need a mutable object (the vector) to be accessible within the log > > +/// function, so we wrap it in the [`ScopeGuard`]: > > +/// ``` > > +/// # use kernel::ScopeGuard; > > +/// fn example3(arg: bool) -> Result { > > +/// let mut vec = > > +/// ScopeGuard::new_with_data(Vec::new(), |v| pr_info!("vec had {} elements\n", v.len())); > > +/// > > +/// vec.try_push(10u8)?; > > +/// if arg { > > +/// return Ok(()); > > +/// } > > +/// vec.try_push(20u8)?; > > +/// Ok(()) > > +/// } > > +/// > > +/// # assert_eq!(example3(false), Ok(())); > > +/// # assert_eq!(example3(true), Ok(())); > > +/// ``` > > +/// > > +/// # Invariants > > +/// > > +/// The value stored in the struct is nearly always `Some(_)`, except between > > +/// [`ScopeGuard::dismiss`] and [`ScopeGuard::drop`]: in this case, it will be `None` as the value > > +/// will have been returned to the caller. Since [`ScopeGuard::dismiss`] consumes the guard, > > +/// callers won't be able to use it anymore. > > +pub struct ScopeGuard<T, F: FnOnce(T)>(Option<(T, F)>); > > + > > +impl<T, F: FnOnce(T)> ScopeGuard<T, F> { > > + /// Creates a new guarded object wrapping the given data and with the given cleanup function. > > + pub fn new_with_data(data: T, cleanup_func: F) -> Self { > > + // INVARIANT: The struct is being initialised with `Some(_)`. > > + Self(Some((data, cleanup_func))) > > + } > > + > > + /// Prevents the cleanup function from running and returns the guarded data. > > + pub fn dismiss(mut self) -> T { > > + // INVARIANT: This is the exception case in the invariant; it is not visible to callers > > + // because this function consumes `self`. > > + self.0.take().unwrap().0 > > + } > > +} > > + > > +impl ScopeGuard<(), Box<dyn FnOnce(())>> { > > How about `fn(())` here as a placeholder? I.e > > impl ScopeGuard<(), fn(())> >
That's simpler, I like it. I'll change this for v2. Thanks!
> Regards, > Boqun > > > + /// Creates a new guarded object with the given cleanup function. > > + pub fn new(cleanup: impl FnOnce()) -> ScopeGuard<(), impl FnOnce(())> { > > + ScopeGuard::new_with_data((), move |_| cleanup()) > > + } > > +} > > + > > +impl<T, F: FnOnce(T)> Deref for ScopeGuard<T, F> { > > + type Target = T; > > + > > + fn deref(&self) -> &T { > > + // The type invariants guarantee that `unwrap` will succeed. > > + &self.0.as_ref().unwrap().0 > > + } > > +} > > + > > +impl<T, F: FnOnce(T)> DerefMut for ScopeGuard<T, F> { > > + fn deref_mut(&mut self) -> &mut T { > > + // The type invariants guarantee that `unwrap` will succeed. > > + &mut self.0.as_mut().unwrap().0 > > + } > > +} > > + > > +impl<T, F: FnOnce(T)> Drop for ScopeGuard<T, F> { > > + fn drop(&mut self) { > > + // Run the cleanup function if one is still present. > > + if let Some((data, cleanup)) = self.0.take() { > > + cleanup(data) > > + } > > + } > > +} > > > > /// Stores an opaque value. > > /// > > -- > > 2.34.1 > >
|  |