From nobody Mon Jun 8 19:43:05 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 93D2D3C0A05 for ; Tue, 26 May 2026 20:54:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779828892; cv=none; b=bfqXV8kOusSPJQqtYtEr4TkMekb2nb16r8Dgxxb8R6CYn6RzyhLT/2EsxVLp0WsNxzDB8iI6JdoqDN777atDOGXPseO5F0mt+8Nm917lYK+vilrZ0Uihjyxi6Uy7ZFe1kx4LW1HsdjCtdQnzQ/QGGfeceqeD8A3JEsCun00AeDA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779828892; c=relaxed/simple; bh=MrTkfmDf83gaBbKz1p+h1nqg8TL6yUkib+CRXlnBFDU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=s3Wl8SBNesPnexYqYciWJ9u52rjLtOHlcYskdiVoWKSZmdekELXKDUL/sa5CtKBU/2UrSxPSjhE2A/+vhwWoWb4wpXk7eHzO6Ci3AHfc1fRBGxTNztZP5cKnDOxonHcHze/3y7L09/A5m88Q510DpGZOxMhgzLfG+mIJGEnD8Wg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=W4iW6iV5; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="W4iW6iV5" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1779828887; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=CTXkVWhfiCkPPMFCSefkBsAtfCVesv2uIIO2EqJJ/8Y=; b=W4iW6iV5i9olb6FxJaE7KWDuSZQCS5Vr9l2X5CHEcmTg8i++jKQBH2yaURIFxKQ8pFtP/5 GnH7oUieCh/EK3z987kPj7jndkNuIPSl2yyVS78pncn7ceZRsef4KQ7obe931AlhMrPCki lpe47yeyb9rxZUTiGXIzHqYLzxCsbkA= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-686-Lu9AwNRWOHuA8Wdb23izvA-1; Tue, 26 May 2026 16:54:39 -0400 X-MC-Unique: Lu9AwNRWOHuA8Wdb23izvA-1 X-Mimecast-MFC-AGG-ID: Lu9AwNRWOHuA8Wdb23izvA_1779828870 Received: from mx-prod-int-10.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-10.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.95]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 95900195609F; Tue, 26 May 2026 20:54:30 +0000 (UTC) Received: from GoldenWind.lan (unknown [10.22.64.238]) by mx-prod-int-10.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id AEE0D1685; Tue, 26 May 2026 20:54:28 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org, nouveau@lists.freedesktop.org Cc: "Gary Guo" , "Ingo Molnar" , "Miguel Ojeda" , "Alice Ryhl" , linux-kernel@vger.kernel.org, "Tamir Duberstein" , "Boqun Feng" , "Peter Zijlstra" , "Benno Lossin" , "Will Deacon" , "Lyude Paul" Subject: [PATCH 1/2] rust: sync: lock: Add Lock::get_mut_pinned() Date: Tue, 26 May 2026 16:41:33 -0400 Message-ID: <20260526205419.1055109-2-lyude@redhat.com> In-Reply-To: <20260526205419.1055109-1-lyude@redhat.com> References: <20260526205419.1055109-1-lyude@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.6 on 10.30.177.95 These two functions are inspired by the Rust stdlib equivalent: https://doc.rust-lang.org/std/sync/struct.Mutex.html#method.get_mut() The idea here is very simple - if the user has access to a Pin<&mut Mutex<=E2=80=A6>>, we can guarantee that no one else can look at the data p= rotected by the lock. Thus in such situations, locking the mutex isn't necessary to access its contents. This can be useful in situations like `Drop` implementations, where we may want to access the contents of a Mutex within a struct before dropping it. So to do this, we add `get_mut_pinned()` to `Lock` - which provides a function to access the inner contents of a Mutex provided a Pin<&mut =E2=80= =A6>. Signed-off-by: Lyude Paul --- rust/kernel/sync/lock.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/rust/kernel/sync/lock.rs b/rust/kernel/sync/lock.rs index 10b6b5e9b024f..5ca36baed34f5 100644 --- a/rust/kernel/sync/lock.rs +++ b/rust/kernel/sync/lock.rs @@ -190,6 +190,17 @@ pub fn try_lock(&self) -> Option> { // that `init` was called. unsafe { B::try_lock(self.state.get()).map(|state| Guard::new(self= , state)) } } + + /// Returns a pinned mutable reference to the underlying data. + /// + /// Because this borrows the lock mutably, no actual locking needs to = take place - as the + /// mutable borrow statically guarantees no new locks can be acquired = while this reference + /// exists. + #[inline(always)] + pub fn get_mut_pinned(self: Pin<&mut Self>) -> Pin<&mut T> { + // SAFETY: We return a pinned T, ensuring we don't move T. + unsafe { self.map_unchecked_mut(|data| data.data.get_mut()) } + } } =20 /// A lock guard. --=20 2.54.0 From nobody Mon Jun 8 19:43:05 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6DCC037DEAD for ; Tue, 26 May 2026 20:54:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779828885; cv=none; b=KHXPKhJUz4+iDAzz1G3RZfp2w9oqy12kirAOPRGus2WAjAeeErz8Cr773q5um+JPyQ6vulzmBDxsOiQvmeug/xAMPa8Rxs4dCY2AWqDjp7JjiH1QnFWNOGlz6imTQJow/EKYjvS7/YNWHmdZc4Zl6XXgOXHZYsDaXmfxHJn4GxI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779828885; c=relaxed/simple; bh=WkWF9E3PYo2ftwN9mjVdhWo+H3ClONdyUEwKj2KXKyg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=bKvaxIBMtJ+3S7gpjkxk5UNStKAEPUrTubcW5VN6IdocOB5/MGrkXKljbWdXJR3gwJdk5DLkwJk+0+2rin4IuBgkKJG6wHiMpsa5wjds1nYTLpM0S6khUmTLrK1aqBpgdkZw8dm4X2RWF4jt/XpMYWc2qD9kodGJITYpvYWZY1c= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=gkForwAC; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="gkForwAC" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1779828882; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=WOfmBRCKxr/gyXVGWymUHEp0qbXUCHLlesjJounLkj0=; b=gkForwAC1Dqe4tjVze7/F5VEAFCkwx4tuzOx4d4+Nw9bxuh/nDppGXNi9oTrZLCezqjhnm VGFcanpUdDEQYUOzuNg5sD1vT7vpSTEKJ0+CfklcdkiDWMZkFBhVOLPoIHzsUYAKOlWLye 7a7FomRidjl5FMi4b7ntoCbstiYCWVA= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-146-FPDUYXV9NXu7KGG5-IvBWw-1; Tue, 26 May 2026 16:54:39 -0400 X-MC-Unique: FPDUYXV9NXu7KGG5-IvBWw-1 X-Mimecast-MFC-AGG-ID: FPDUYXV9NXu7KGG5-IvBWw_1779828877 Received: from mx-prod-int-10.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-10.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.95]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id B1B03195608B; Tue, 26 May 2026 20:54:36 +0000 (UTC) Received: from GoldenWind.lan (unknown [10.22.64.238]) by mx-prod-int-10.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 4B9961681; Tue, 26 May 2026 20:54:34 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org, nouveau@lists.freedesktop.org Cc: "Gary Guo" , "Ingo Molnar" , "Miguel Ojeda" , "Alice Ryhl" , linux-kernel@vger.kernel.org, "Tamir Duberstein" , "Boqun Feng" , "Peter Zijlstra" , "Shankari Anand" , "Viresh Kumar" , "Benno Lossin" , "Will Deacon" , "Lyude Paul" Subject: [PATCH 2/2] rust: sync: Introduce LazyInit Date: Tue, 26 May 2026 16:41:34 -0400 Message-ID: <20260526205419.1055109-3-lyude@redhat.com> In-Reply-To: <20260526205419.1055109-1-lyude@redhat.com> References: <20260526205419.1055109-1-lyude@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.6 on 10.30.177.95 Content-Type: text/plain; charset="utf-8" SetOnce is nice, but it does have one problem - you can't use it with fallible initializers. While we will be adding support for doing that with SetOnce, this leads into another problem: There's no way for racing callers to actually block on the initialization of SetOnce, which makes it a difficult type to use safely for situations where we want to initialize data fallibly once, and then provide access to it to multiple users at once until drop. So to solve this, introduce a new type: LazyInit. LazyInit is like SetOnce with a couple of important differences: * It can't be used in const context * It can handle in-place fallible initializers. * It uses a Mutex for synchronization instead of an atomic, allowing callers to block on initialization. * It requires its contents already be Send + Sync, since the Mutex protects initializing data and not the data itself. Signed-off-by: Lyude Paul --- rust/kernel/sync.rs | 2 + rust/kernel/sync/lazy_init.rs | 354 ++++++++++++++++++++++++++++++++++ 2 files changed, 356 insertions(+) create mode 100644 rust/kernel/sync/lazy_init.rs diff --git a/rust/kernel/sync.rs b/rust/kernel/sync.rs index 993dbf2caa0e3..d18c26c56c4d2 100644 --- a/rust/kernel/sync.rs +++ b/rust/kernel/sync.rs @@ -15,6 +15,7 @@ pub mod barrier; pub mod completion; mod condvar; +mod lazy_init; pub mod lock; mod locked_by; pub mod poll; @@ -25,6 +26,7 @@ pub use arc::{Arc, ArcBorrow, UniqueArc}; pub use completion::Completion; pub use condvar::{new_condvar, CondVar, CondVarTimeoutResult}; +pub use lazy_init::*; pub use lock::global::{global_lock, GlobalGuard, GlobalLock, GlobalLockBac= kend, GlobalLockedBy}; pub use lock::mutex::{new_mutex, Mutex, MutexGuard}; pub use lock::spinlock::{new_spinlock, SpinLock, SpinLockGuard}; diff --git a/rust/kernel/sync/lazy_init.rs b/rust/kernel/sync/lazy_init.rs new file mode 100644 index 0000000000000..707af369e965d --- /dev/null +++ b/rust/kernel/sync/lazy_init.rs @@ -0,0 +1,354 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Thread-safe Lazy-[`Init`] and [`PinInit`] +use crate::{ + prelude::*, + sync::{ + LockClassKey, + Mutex, + MutexGuard, // + }, +}; +use core::{ + mem::{ + self, + MaybeUninit, // + }, + ptr, +}; +use pin_init::{ + pin_init, + PinInit, // +}; + +/// # Invariants +/// +/// - `data` is guaranteed to be initialized if `is_init` is `true`. +/// - `data` is only written to once from `LazyInit::init()`, and once fro= m `LazyInit::reset()` +/// (which cannot race with `LazyInit::init()` due to requiring a `&mut`= reference to the +/// `LazyInit`). +#[pin_data(PinnedDrop)] +struct Inner { + #[pin] + data: MaybeUninit, + is_init: bool, +} + +#[pinned_drop] +impl PinnedDrop for Inner { + fn drop(self: Pin<&mut Self>) { + if self.is_init { + // SAFETY: The only contents from `this` that we move is `is_i= nit`. `is_init` is Unpin, + // making this OK. + let this =3D unsafe { self.get_unchecked_mut() }; + + // INVARIANT: This is the only place other then `LazyInit::pop= ulate()` where we write to + // `data`, and this function requires &mut self - ensuring it = cannot race with + // `LazyInit::populate()`. + // SAFETY: Since `is_init` is true, our type invariants guaran= tee `data` is initialized. + unsafe { this.data.assume_init_drop() }; + + this.is_init =3D false; + } + } +} + +/// Errors that can occur during [`LazyInit::new`]. +#[derive(Debug)] +pub enum LazyInitError<'a, T: Send + Sync, E =3D Error> { + /// The [`LazyInit`] has already been initialized. + /// + /// `self.0` contains a reference to the previously initialized value. + AlreadyInit(&'a T), + /// An error occurred during initialization. + DuringInit(E), +} + +impl<'a, T: Send + Sync, E> From for LazyInitError<'a, T, E> { + fn from(value: E) -> Self { + Self::DuringInit(value) + } +} + +/// Creates a [`LazyInit`] initialiser with the given name and newly-creat= ed lock class. +/// +/// It uses the name if one is given, otherwise it generates one based on = the file name and line +/// number. +#[macro_export] +macro_rules! new_lazy_init { + ($( $name:literal )?) =3D> { + $crate::sync::LazyInit::new($crate::optional_name!($($name)?), $cr= ate::static_lock_class!()) + }; +} +pub use new_lazy_init; + +/// A thread-safe container that allows thread-safe single-time initializa= tion of its contents, +/// which can then have shared references taken to its contents. It is sim= ilar to [`SetOnce`] except +/// that it uses a [`Mutex`], it can be populated using fallible [`PinInit= `] initializers, and +/// multiple callers attempting to initialize at the same time will block = until the conetents of the +/// [`LazyInit`] is finished initializing. +/// +/// This type cannot be used in `const` contexts, however, unlike [`SetOnc= e`] users of this type are +/// able to block if another thread is busy populating the [`SetOnce`]. Th= is also allows the use of +/// fallible initializers for population. +/// +/// This type internally uses a [`Mutex`] for synchronizing creation of `T= `, but allows access to +/// `T` outside of lock post-init. This means that only the initialization= of the value is protected +/// by the lock, and thus `T` must provide it's own synchronization by imp= lementing `Send` + `Sync`. +/// +/// # Examples +/// +/// ``` +/// use kernel::sync::{ +/// LazyInit, +/// LazyInitError, +/// Arc, +/// new_lazy_init, // +/// }; +/// +/// struct Inner { +/// a: u8, +/// } +/// +/// #[pin_data] +/// struct Example { +/// #[pin] +/// lazy: LazyInit>, +/// } +/// +/// impl Example { +/// fn new() -> impl PinInit { +/// pin_init!(Self { +/// lazy <- new_lazy_init!(), +/// }) +/// } +/// } +/// +/// // Allocate a boxed `Example`. +/// let e =3D KBox::pin_init(Example::new(), GFP_KERNEL)?; +/// +/// assert!(e.lazy.init(Arc::init(init!(Inner { a: 42 }), GFP_KERNEL)).is_= ok()); +/// +/// // `as_ref()` can be used to get a reference to the contents of a `Laz= yInit` if its initialized. +/// assert_eq!(e.lazy.as_ref().unwrap().a, 42); +/// +/// // If `LazyInit::init()` fails due to the `LazyInit` already being ini= tialized, a reference to +/// // the initialized data can still be retrieved from the error. +/// match e.lazy.init(Arc::init(init!(Inner { a: 54 }), GFP_KERNEL)) { +/// Err(LazyInitError::AlreadyInit(ret)) =3D> assert_eq!(ret.a, 42), +/// _ =3D> unreachable!(), +/// } +/// # Ok::<(), Error>(()) +/// ``` +/// +/// [`SetOnce`]: super::SetOnce +#[pin_data] +pub struct LazyInit { + #[pin] + inner: Mutex>, +} + +impl LazyInit { + /// Create a new initializer for an empty [`LazyInit`]. + pub fn new(name: &'static CStr, key: Pin<&'static LockClassKey>) -> im= pl PinInit { + pin_init!(Self { + inner <- Mutex::new( + pin_init!(Inner { + data <- MaybeUninit::uninit(), + is_init: false, + }), + name, + key + ) + }) + } + + /// Retrieve the contents of `inner.data` and extend their lifetime. + /// + /// # Safety + /// + /// The caller guarantees that `self.inner.data` has been previously i= nitialized. + #[inline(always)] + unsafe fn data<'a>(&'a self, inner: &MutexGuard<'_, Inner>) -> &'a = T { + // SAFETY: + // - Our safety contract guarantees `inner.data` is initialized. + // - `T` is `Send` + `Sync`, and thus does not need the `Mutex` fo= r synchronization, making + // it safe to hold onto outside of the lock. + // - We're guaranteed the container of `T` will not be written to = via `Inner`s type + // invariants until `Drop`, ensuring it remains populated for th= e lifetime of 'a. + unsafe { mem::transmute::<&_, &'a _>(inner.data.assume_init_ref())= } + } + + /// Attempt to init the contents of [`LazyInit`] and return a referenc= e to its contents. + /// + /// If the contents of the [`LazyInit`] were already initialized, they= will not be + /// re-initialized. + /// + /// Returns: + /// + /// - `Ok(&T)` if the container was initialized successfully, or if it= was already initialized + /// by another user. + /// - [`Err(LazyInitError::AlreadyInit)`](LazyInitError::AlreadyInit) = if the container was + /// already initialized. This error contains a reference to the cont= ents of the [`LazyInit`]. + /// - [`Err(LazyInitError::DuringInit)`](LazyInitError::DuringInit) if= an error was encountered + /// while trying to initialize this [`LazyInit`]. + pub fn init<'a, E>( + &'a self, + init: impl PinInit, + ) -> Result<&'a T, LazyInitError<'a, T, E>> { + let mut inner =3D self.inner.lock(); + let do_init =3D !inner.is_init; + + if do_init { + // SAFETY: The only thing that we move is `is_init`, which is = Unpin. + let inner =3D unsafe { inner.as_mut().get_unchecked_mut() }; + + // INVARIANT: This is the only place we can write to `data` be= fore Drop, fulfilling + // `Inner`'s relevant type invariant. + // + // SAFETY: + // - Via Inner's invariants, since we checked `is_init` we're = guaranteed `data` is + // uninitialized. + // - We do not touch `data` at any point after this if we fail. + // - This does not move `data`. + unsafe { init.__pinned_init(inner.data.as_mut_ptr()) }?; + + inner.is_init =3D true; + } + + // INVARIANT: This code is only reachable if `data` was, either pr= eviously or just now, + // initialized with a valid instance of `T`. Otherwise we've never= written to it and `data` + // is guaranteed to be uninitialized, fulfilling `Inner`s type inv= ariants regarding + // `data` initialization state. + + // SAFETY: We confirmed `data` is initialized above. + let ret =3D unsafe { self.data(&inner) }; + + match do_init { + true =3D> Ok(ret), + false =3D> Err(LazyInitError::AlreadyInit(ret)), + } + } + + /// Get a reference to the contained object. + /// + /// Returns [`None`] if this [`LazyInit`] is empty. + pub fn as_ref<'a>(&'a self) -> Option<&'a T> { + let inner =3D self.inner.lock(); + + inner.is_init.then(|| { + // SAFETY: This closure can only execute if `inner.is_init` is= true, guaranteeing + // `inner.data` is initialized via `Inner`s type invariants. + unsafe { self.data(&inner) } + }) + } + + /// Release the contents of the [`LazyInit`]. + /// + /// Since this call borrows the [`LazyInit`] mutably, no locking actua= lly needs to take place - + /// as parallel lock acquisitions are prevented via the compiler enfor= cing Rust's borrowing + /// rules. + /// + /// Returns [`true`] if `self` was initialized before calling this fun= ction. + pub fn reset(self: Pin<&mut Self>) -> bool { + let inner =3D self.project().inner.get_mut_pinned(); + let was_init =3D inner.is_init; + + // SAFETY: + // - We drop in place, and therefore do not move `inner` + // - The `PinnedDrop` implementation of `Inner` leaves it in a wel= l-defined + // state, so we do not need to worry about UB from further usage. + unsafe { ptr::drop_in_place(inner.get_unchecked_mut()) }; + + was_init + } +} + +#[kunit_tests(rust_lazy_init)] +mod tests { + use super::*; + use core::ops::Deref; + use pin_init::{ + init_from_closure, + stack_pin_init, // + }; + + #[derive(Debug)] + #[pin_data] + struct LazyInitTest {} + + impl LazyInitTest { + fn init_ok() -> impl Init { + init!(LazyInitTest {}) + } + + fn init_err(e: Error) -> impl Init { + // SAFETY: + // This initializer is intended to fail for testing purposes, = and does not actually + // initialize any data meaning: + // + // - We don't need to worry about returning Ok(()). + // - We have nothing to actually clean in the slot. + // - Since there is no data in the slot, nothing can meaningfu= lly move and we have no + // pinning invariants. + unsafe { init_from_closure(move |_| Err(e)) } + } + } + + fn try_lazy_init(once: &LazyInit) -> Result<(), Error> { + // It should start as being empty. + assert!((*once).as_ref().is_none()); + + // Try populating it a single time, this should succeed. + let res =3D once.init(LazyInitTest::init_ok()).map_err(|_| EINVAL)= ?; + assert!((*once).as_ref().is_some()); + + // Populating a second time should fail and return the contents. + match once.init(LazyInitTest::init_ok()) { + Err(LazyInitError::AlreadyInit(data)) =3D> assert!(core::ptr::= eq(data, res)), + r @ _ =3D> panic!("Calling once.init() twice returned unexpect= ed value: {r:?}"), + } + + // And it should still hold a value + assert!((*once).as_ref().is_some()); + + Ok(()) + } + + #[test] + fn lazy_init() -> Result { + stack_pin_init!(let once: LazyInit =3D new_lazy_init= !()); + + try_lazy_init(&once)?; + Ok(()) + } + + #[test] + fn lazy_init_error() -> Result { + stack_pin_init!(let once: LazyInit =3D new_lazy_init= !()); + + // Make sure it starts as empty. + assert!((*once).as_ref().is_none()); + + // Try populating it with a initializer that fails. + match once.init(LazyInitTest::init_err(EJUKEBOX)) { + Err(LazyInitError::DuringInit(e)) =3D> assert_eq!(e, EJUKEBOX), + r @ _ =3D> panic!( + "Calling once.init() with a failing initializer returned a= n unexpected value: {r:?}" + ), + } + + try_lazy_init(&once)?; + Ok(()) + } + + #[test] + fn lazy_init_reset() -> Result { + stack_pin_init!(let once: LazyInit =3D new_lazy_init= !()); + + try_lazy_init(&once)?; + assert_eq!(once.as_mut().reset(), true); + assert_eq!(once.as_mut().reset(), false); + try_lazy_init(&once) + } +} --=20 2.54.0