From 9fb0e4e8b20c722e1616c9a42cd80fd4755ff753 Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Wed, 8 Oct 2025 22:08:00 +0200 Subject: [PATCH] add `[pin_]init_scope` to execute code before creating an initializer In more complex cases, initializers need to run arbitrary code before assigning initializers to fields. While this is possible using the underscore codeblock feature (`_: {}`), values returned by such functions cannot be used from later field initializers. The two new functinos `[pin_]init_scope` allow users to first run some fallible code and then return an initializer which the function turns into a single initializer. This permits using the same value multiple times by different fields. Reviewed-by: Gary Guo Reviewed-by: Danilo Krummrich Signed-off-by: Benno Lossin --- CHANGELOG.md | 4 +++ src/lib.rs | 87 +++++++++++++++++++++++++++++++++++++++++++++ tests/init-scope.rs | 30 ++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 tests/init-scope.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 137cf92a..ab61f6f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- `[pin_]init_scope` functions to run arbitrary code inside of an initializer. + ### Changed - `#[pin_data]` now generates a `*Projection` struct similar to the `pin-project` crate. diff --git a/src/lib.rs b/src/lib.rs index dd553212..8dc9dd5a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1392,6 +1392,93 @@ where unsafe { pin_init_from_closure(init) } } +/// Construct an initializer in a closure and run it. +/// +/// Returns an initializer that first runs the closure and then the initializer returned by it. +/// +/// See also [`init_scope`]. +/// +/// # Examples +/// +/// ``` +/// # use pin_init::*; +/// # #[pin_data] +/// # struct Foo { a: u64, b: isize } +/// # struct Bar { a: u32, b: isize } +/// # fn lookup_bar() -> Result { todo!() } +/// # struct Error; +/// fn init_foo() -> impl PinInit { +/// pin_init_scope(|| { +/// let bar = lookup_bar()?; +/// Ok(try_pin_init!(Foo { a: bar.a.into(), b: bar.b }? Error)) +/// }) +/// } +/// ``` +/// +/// This initializer will first execute `lookup_bar()`, match on it, if it returned an error, the +/// initializer itself will fail with that error. If it returned `Ok`, then it will run the +/// initializer returned by the [`try_pin_init!`] invocation. +pub fn pin_init_scope(make_init: F) -> impl PinInit +where + F: FnOnce() -> Result, + I: PinInit, +{ + // SAFETY: + // - If `make_init` returns `Err`, `Err` is returned and `slot` is completely uninitialized, + // - If `make_init` returns `Ok`, safety requirement are fulfilled by `init.__pinned_init`. + // - The safety requirements of `init.__pinned_init` are fulfilled, since it's being called + // from an initializer. + unsafe { + pin_init_from_closure(move |slot: *mut T| -> Result<(), E> { + let init = make_init()?; + init.__pinned_init(slot) + }) + } +} + +/// Construct an initializer in a closure and run it. +/// +/// Returns an initializer that first runs the closure and then the initializer returned by it. +/// +/// See also [`pin_init_scope`]. +/// +/// # Examples +/// +/// ``` +/// # use pin_init::*; +/// # struct Foo { a: u64, b: isize } +/// # struct Bar { a: u32, b: isize } +/// # fn lookup_bar() -> Result { todo!() } +/// # struct Error; +/// fn init_foo() -> impl Init { +/// init_scope(|| { +/// let bar = lookup_bar()?; +/// Ok(try_init!(Foo { a: bar.a.into(), b: bar.b }? Error)) +/// }) +/// } +/// ``` +/// +/// This initializer will first execute `lookup_bar()`, match on it, if it returned an error, the +/// initializer itself will fail with that error. If it returned `Ok`, then it will run the +/// initializer returned by the [`try_init!`] invocation. +pub fn init_scope(make_init: F) -> impl Init +where + F: FnOnce() -> Result, + I: Init, +{ + // SAFETY: + // - If `make_init` returns `Err`, `Err` is returned and `slot` is completely uninitialized, + // - If `make_init` returns `Ok`, safety requirement are fulfilled by `init.__init`. + // - The safety requirements of `init.__init` are fulfilled, since it's being called from an + // initializer. + unsafe { + init_from_closure(move |slot: *mut T| -> Result<(), E> { + let init = make_init()?; + init.__init(slot) + }) + } +} + // SAFETY: the `__init` function always returns `Ok(())` and initializes every field of `slot`. unsafe impl Init for T { unsafe fn __init(self, slot: *mut T) -> Result<(), Infallible> { diff --git a/tests/init-scope.rs b/tests/init-scope.rs new file mode 100644 index 00000000..a1c0bf5f --- /dev/null +++ b/tests/init-scope.rs @@ -0,0 +1,30 @@ +#![allow(dead_code)] +#![cfg_attr(not(RUSTC_LINT_REASONS_IS_STABLE), feature(lint_reasons))] + +use pin_init::*; + +#[pin_data] +pub struct MyStruct { + a: usize, + b: isize, +} + +fn foo() -> Result { + Ok(0) +} + +impl MyStruct { + pub fn new() -> impl Init { + init_scope(|| { + let a = foo()?; + Ok(try_init!(Self { a, b: 42 }?())) + }) + } + + pub fn new2() -> impl PinInit { + pin_init_scope(|| { + let a = foo()?; + Ok(try_pin_init!(Self { a, b: 42 }?())) + }) + } +}