[1/5] rust: types: introduce `ScopeGuard`
Commit Message
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(-)
Comments
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(())>
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
>
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
> >
On Thu Jan 19, 2023 at 6:40 PM CET, 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>
> ---
Reviewed-by: Vincenzo Palazzo <vincenzopalazzodev@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(())>> {
> + /// 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
@@ -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(())>> {
+ /// 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.
///