From: Boqun Feng <boqun.feng@gmail.com>
To: Oliver Mangold <oliver.mangold@pm.me>
Cc: "Miguel Ojeda" <ojeda@kernel.org>,
"Alex Gaynor" <alex.gaynor@gmail.com>,
"Gary Guo" <gary@garyguo.net>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Benno Lossin" <benno.lossin@proton.me>,
"Andreas Hindborg" <a.hindborg@kernel.org>,
"Alice Ryhl" <aliceryhl@google.com>,
"Trevor Gross" <tmgross@umich.edu>,
"Asahi Lina" <lina@asahilina.net>,
rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH v7 4/4] rust: adding OwnableRefCounted and SimpleOwnableRefCounted
Date: Mon, 10 Mar 2025 10:47:56 -0700 [thread overview]
Message-ID: <Z88lzDPsO7UStQ85@boqun-archlinux> (raw)
In-Reply-To: <20250310-unique-ref-v7-4-4caddb78aa05@pm.me>
On Mon, Mar 10, 2025 at 10:57:47AM +0000, Oliver Mangold wrote:
> Types implementing one of these traits can safely convert between an
> ARef<T> and an Owned<T>.
>
> Signed-off-by: Oliver Mangold <oliver.mangold@pm.me>
> ---
> rust/kernel/types.rs | 275 +++++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 275 insertions(+)
>
> diff --git a/rust/kernel/types.rs b/rust/kernel/types.rs
> index e6f3308f931d90718d405443c3034a216388e0af..3e703701b2bccf1a440f4064b6dd90afb204d937 100644
> --- a/rust/kernel/types.rs
> +++ b/rust/kernel/types.rs
> @@ -552,6 +552,12 @@ fn from(b: &T) -> Self {
> }
> }
>
> +impl<T: OwnableRefCounted> From<Owned<T>> for ARef<T> {
> + fn from(b: Owned<T>) -> Self {
> + T::into_shared(b)
> + }
> +}
> +
> impl<T: RefCounted> Drop for ARef<T> {
> fn drop(&mut self) {
> // SAFETY: The type invariants guarantee that the `ARef` owns the reference
> @@ -669,6 +675,275 @@ fn drop(&mut self) {
> }
> }
>
> +/// A trait for objects that can be wrapped in either one of the reference types [`Owned`] and
> +/// [`ARef`].
> +///
> +/// # Safety
> +///
> +/// Implementers must ensure that:
> +///
> +/// - Both the safety requirements for [`Ownable`] and [`RefCounted`] are fulfilled.
> +/// - The uniqueness invariant of [`Owned`] is upheld until dropped.
> +/// - [`try_from_shared()`](OwnableRefCounted::into_shared) only returns an [`Owned`] if exactly
> +/// one [`ARef`] exists.
> +/// - [`into_shared()`](OwnableRefCounted::into_shared) set the reference count to the value which
> +/// the returned [`ARef`] expects for an object with a single reference
> +/// in existence. This implies that if [`into_shared()`](OwnableRefCounted::into_shared) is left
> +/// on the default implementation, which just rewraps the underlying object, the reference count
> +/// needs not to be modified when converting a [`Owned`] to an [`ARef`].
> +///
> +/// # Examples
> +///
> +/// A minimal example implementation of [`OwnableRefCounted`], [`Ownable`] and its usage with
> +/// [`ARef`] and [`Owned`] looks like this:
> +///
> +/// ```
> +/// # #![expect(clippy::disallowed_names)]
> +/// use core::cell::Cell;
> +/// use core::ptr::NonNull;
> +/// use kernel::alloc::{flags, kbox::KBox, AllocError};
> +/// use kernel::types::{
> +/// ARef, RefCounted, Owned, Ownable, OwnableRefCounted,
> +/// };
> +///
> +/// struct Foo {
> +/// refcount: Cell<usize>,
> +/// }
> +///
> +/// impl Foo {
> +/// fn new() -> Result<Owned<Self>, AllocError> {
> +/// // Use a `KBox` to handle the actual allocation.
> +/// let result = KBox::new(
> +/// Foo {
> +/// refcount: Cell::new(1),
> +/// },
> +/// flags::GFP_KERNEL,
> +/// )?;
> +/// let result = NonNull::new(KBox::into_raw(result))
> +/// .expect("Raw pointer to newly allocation KBox is null, this should never happen.");
> +/// // SAFETY: We just allocated the `Foo`, thus it is valid.
> +/// Ok(unsafe { Owned::from_raw(result) })
> +/// }
> +/// }
> +///
> +/// // SAFETY: We increment and decrement each time the respective function is called and only free
> +/// // the `Foo` when the refcount reaches zero.
> +/// unsafe impl RefCounted for Foo {
> +/// fn inc_ref(&self) {
> +/// self.refcount.replace(self.refcount.get() + 1);
> +/// }
> +///
> +/// unsafe fn dec_ref(this: NonNull<Self>) {
> +/// // SAFETY: The underlying object is always valid when the function is called.
> +/// let refcount = unsafe { &this.as_ref().refcount };
> +/// let new_refcount = refcount.get() - 1;
> +/// if new_refcount == 0 {
> +/// // The `Foo` will be dropped when `KBox` goes out of scope.
> +/// // SAFETY: The `Box<Foo>` is still alive as the old refcount is 1.
> +/// unsafe { KBox::from_raw(this.as_ptr()) };
> +/// } else {
> +/// refcount.replace(new_refcount);
> +/// }
> +/// }
> +/// }
> +///
> +/// // SAFETY: We only convert into an `Owned` when the refcount is 1.
> +/// unsafe impl OwnableRefCounted for Foo {
> +/// fn try_from_shared(this: ARef<Self>) -> Result<Owned<Self>, ARef<Self>> {
> +/// if this.refcount.get() == 1 {
> +/// // SAFETY: The `Foo` is still alive as the refcount is 1.
> +/// Ok(unsafe { Owned::from_raw(ARef::into_raw(this)) })
> +/// } else {
> +/// Err(this)
> +/// }
> +/// }
> +/// }
> +///
> +/// // SAFETY: We are not `AlwaysRefCounted`.
> +/// unsafe impl Ownable for Foo {
> +/// unsafe fn release(this: NonNull<Self>) {
> +/// // SAFETY: Using `dec_ref()` from `RefCounted` to release is okay, as the refcount is
> +/// // always 1 for an `Owned<Foo>`.
> +/// unsafe{ Foo::dec_ref(this) };
> +/// }
> +/// }
> +///
> +/// let foo = Foo::new().unwrap();
> +/// let mut foo = ARef::from(foo);
> +/// {
> +/// let bar = foo.clone();
> +/// assert!(Owned::try_from(bar).is_err());
> +/// }
> +/// assert!(Owned::try_from(foo).is_ok());
> +/// ```
> +pub unsafe trait OwnableRefCounted: RefCounted + Ownable + Sized {
> + /// Checks if the [`ARef`] is unique and convert it to an [`Owned`] it that is that case.
> + /// Otherwise it returns again an [`ARef`] to the same underlying object.
> + fn try_from_shared(this: ARef<Self>) -> Result<Owned<Self>, ARef<Self>>;
> + /// Converts the [`Owned`] into an [`ARef`].
> +
> + fn into_shared(this: Owned<Self>) -> ARef<Self> {
> + // SAFETY: Safe by the requirements on implementing the trait.
> + unsafe { ARef::from_raw(Owned::into_raw(this)) }
> + }
> +}
> +
> +/// This trait allows to implement all of [`Ownable`], [`RefCounted`] and
> +/// [`OwnableRefCounted`] together in a simplified way,
> +/// only requiring to provide the methods [`inc_ref()`](SimpleOwnableRefCounted::inc_ref),
> +/// [`dec_ref()`](SimpleOwnableRefCounted::dec_ref),
> +/// and [`is_unique()`](SimpleOwnableRefCounted::is_unique).
> +///
> +/// For non-standard cases where conversion between [`Ownable`] and [`RefCounted`] needs
> +/// or [`Ownable::release()`] and [`RefCounted::dec_ref()`] cannot be the same method,
> +/// [`Ownable`], [`RefCounted`] and [`OwnableRefCounted`] should be implemented manually.
> +///
> +/// # Safety
> +///
> +/// Implementers must ensure that:
> +///
> +/// - Both the safety requirements as for [`Ownable`] and [`RefCounted`] are fulfilled.
> +/// - [`is_unique`](SimpleOwnableRefCounted::is_unique) must only return `true` in case only one
> +/// [`ARef`] exists and it is impossible for one to be obtained other than by cloning an existing
> +/// [`ARef`] or converting an [`Owned`] to an [`ARef`].
> +/// - It is safe to convert an unique [`ARef`] into an [`Owned`] simply by re-wrapping the
> +/// underlying object without modifying the refcount.
> +///
> +/// # Examples
> +///
> +/// A minimal example implementation of [`SimpleOwnableRefCounted`]
> +/// and its usage with [`ARef`] and [`Owned`] looks like this:
> +///
> +/// ```
> +/// # #![expect(clippy::disallowed_names)]
> +/// use core::cell::Cell;
> +/// use core::ptr::NonNull;
> +/// use kernel::alloc::{flags, kbox::KBox, AllocError};
> +/// use kernel::types::{
> +/// ARef, SimpleOwnableRefCounted, Owned,
> +/// };
> +///
> +/// struct Foo {
> +/// refcount: Cell<usize>,
> +/// }
> +///
> +/// impl Foo {
> +/// fn new() -> Result<Owned<Self>, AllocError> {
> +/// // Use a KBox to handle the actual allocation.
> +/// let result = KBox::new(
> +/// Foo {
> +/// refcount: Cell::new(1),
> +/// },
> +/// flags::GFP_KERNEL,
> +/// )?;
> +/// let result = NonNull::new(KBox::into_raw(result))
> +/// .expect("Raw pointer to newly allocation KBox is null, this should never happen.");
> +/// // SAFETY: We just allocated the `Foo`, thus it is valid.
> +/// Ok(unsafe { Owned::from_raw(result) })
> +/// }
> +/// }
> +///
> +/// // SAFETY: we ensure that:
> +/// // - The `Foo` is only dropped when the refcount is zero.
> +/// // - `is_unique()` only returns `true` when the refcount is 1.
> +/// unsafe impl SimpleOwnableRefCounted for Foo {
> +/// fn inc_ref(&self) {
> +/// self.refcount.replace(self.refcount.get() + 1);
> +/// }
> +///
> +/// unsafe fn dec_ref(this: NonNull<Self>) {
> +/// // SAFETY: The underlying object is always valid when the function is called.
> +/// let refcount = unsafe { &this.as_ref().refcount };
> +/// let new_refcount = refcount.get() - 1;
> +/// if new_refcount == 0 {
> +/// // The `Foo` will be dropped when KBox goes out of scope.
> +/// // SAFETY: The `Box<Foo>` is still alive as the old refcount is 1.
> +/// unsafe { KBox::from_raw(this.as_ptr()) };
> +/// } else {
> +/// refcount.replace(new_refcount);
> +/// }
> +/// }
> +///
> +/// fn is_unique(&self) -> bool {
> +/// self.refcount.get() == 1
> +/// }
> +/// }
> +///
> +/// let foo = Foo::new().unwrap();
> +/// let mut foo = ARef::from(foo);
> +/// {
> +/// let bar = foo.clone();
> +/// assert!(Owned::try_from(bar).is_err());
> +/// }
> +/// assert!(Owned::try_from(foo).is_ok());
> +/// ```
> +pub unsafe trait SimpleOwnableRefCounted {
Can you make this trait as a sub-trait of RefCounted:
pub unsafe trait SimpleOwnableRefCounted: RefCounted {
fn is_unique(&self) -> bool;
}
?
> + /// Checks if exactly one [`ARef`] to the object exists. In case the object is [`Sync`], the
> + /// check needs to be race-free.
> + fn is_unique(&self) -> bool;
> +
> + /// Increments the reference count on the object.
> + fn inc_ref(&self);
> +
> + /// Decrements the reference count on the object when the [`SimpleOwnableRefCounted`] is
Should be:
"... when ARef<SimpleOwnableRefCounted> or
Owned<SimpleOwnableRefCounted> is dropped"
?
> + /// dropped.
> + ///
> + /// Frees the object when the count reaches zero.
It may not end up freeing the object, because ARef<..> only tracks the
Rust side of refcounting, we should avoid mentioning "refcount reaching
to zero" here.
Regards,
Boqun
> + ///
> + /// # Safety
> + ///
> + /// The safety constraints for [`RefCounted::dec_ref`] and [`Ownable::release`] both apply to
> + /// this method, as it will be used to implement both of these traits.
> + unsafe fn dec_ref(obj: NonNull<Self>);
> +}
> +
> +#[cfg_attr(RUSTC_HAS_DO_NOT_RECOMMEND, diagnostic::do_not_recommend)]
> +// SAFETY: Safe by the requirements on implementation of [`SimpleOwnableRefCounted`].
> +unsafe impl<T: SimpleOwnableRefCounted> OwnableRefCounted for T {
> + fn try_from_shared(this: ARef<Self>) -> Result<Owned<Self>, ARef<Self>> {
> + if T::is_unique(&*this) {
> + // SAFETY: Safe by the requirements on implementation of [`SimpleOwnable`].
> + Ok(unsafe { Owned::from_raw(ARef::into_raw(this)) })
> + } else {
> + Err(this)
> + }
> + }
> +}
> +
> +#[cfg_attr(RUSTC_HAS_DO_NOT_RECOMMEND, diagnostic::do_not_recommend)]
> +// SAFETY: Safe by the requirements on implementation of [`SimpleOwnableRefCounted`].
> +unsafe impl<T: SimpleOwnableRefCounted> Ownable for T {
> + unsafe fn release(this: NonNull<Self>) {
> + // SAFETY: Safe by the requirements on implementation of
> + // [`SimpleOwnableRefCounted::dec_ref()`].
> + unsafe { SimpleOwnableRefCounted::dec_ref(this) };
> + }
> +}
> +
> +#[cfg_attr(RUSTC_HAS_DO_NOT_RECOMMEND, diagnostic::do_not_recommend)]
> +// SAFETY: Safe by the requirements on implementation of [`SimpleOwnableRefCounted`].
> +unsafe impl<T: SimpleOwnableRefCounted> RefCounted for T {
> + fn inc_ref(&self) {
> + SimpleOwnableRefCounted::inc_ref(self);
> + }
> +
> + unsafe fn dec_ref(this: NonNull<Self>) {
> + // SAFETY: Safe by the requirements on implementation of
> + // [`SimpleOwnableRefCounted::dec_ref()`].
> + unsafe { SimpleOwnableRefCounted::dec_ref(this) };
> + }
> +}
> +
> +impl<T: OwnableRefCounted> TryFrom<ARef<T>> for Owned<T> {
> + type Error = ARef<T>;
> + /// Tries to convert the [`ARef`] to an [`Owned`] by calling
> + /// [`try_from_shared()`](OwnableRefCounted::try_from_shared). In case the [`ARef`] is not
> + /// unique, it returns again an [`ARef`] to the same underlying object.
> + fn try_from(b: ARef<T>) -> Result<Owned<T>, Self::Error> {
> + T::try_from_shared(b)
> + }
> +}
> +
> /// A sum type that always holds either a value of type `L` or `R`.
> ///
> /// # Examples
>
> --
> 2.48.1
>
>
next prev parent reply other threads:[~2025-03-10 17:49 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-03-10 10:57 [PATCH v7 0/4] New trait OwnableRefCounted for ARef<->Owned conversion Oliver Mangold
2025-03-10 10:57 ` [PATCH v7 1/4] rust: types: Add Ownable/Owned types Oliver Mangold
2025-03-10 17:04 ` Boqun Feng
2025-03-10 10:57 ` [PATCH v7 2/4] rust: rename AlwaysRefCounted to RefCounted Oliver Mangold
2025-03-10 17:10 ` Boqun Feng
2025-03-10 10:57 ` [PATCH v7 3/4] rust: kbuild: provide `RUSTC_HAS_DO_NOT_RECOMMEND` symbol Oliver Mangold
2025-03-10 10:57 ` [PATCH v7 4/4] rust: adding OwnableRefCounted and SimpleOwnableRefCounted Oliver Mangold
2025-03-10 17:47 ` Boqun Feng [this message]
2025-03-12 10:37 ` Oliver Mangold
2025-03-10 15:42 ` [PATCH v7 0/4] New trait OwnableRefCounted for ARef<->Owned conversion Miguel Ojeda
2025-03-12 9:19 ` Oliver Mangold
2025-03-12 12:02 ` Miguel Ojeda
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=Z88lzDPsO7UStQ85@boqun-archlinux \
--to=boqun.feng@gmail.com \
--cc=a.hindborg@kernel.org \
--cc=alex.gaynor@gmail.com \
--cc=aliceryhl@google.com \
--cc=benno.lossin@proton.me \
--cc=bjorn3_gh@protonmail.com \
--cc=gary@garyguo.net \
--cc=lina@asahilina.net \
--cc=linux-kernel@vger.kernel.org \
--cc=ojeda@kernel.org \
--cc=oliver.mangold@pm.me \
--cc=rust-for-linux@vger.kernel.org \
--cc=tmgross@umich.edu \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.