Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,6 @@ trybuild = { version = "1.0", features = ["diff"] }
macrotest = "1.0"
# needed for macrotest, have to enable verbatim feature to be able to format `&raw` expressions.
prettyplease = { version = "0.2", features = ["verbatim"] }

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(NO_UI_TESTS)', 'cfg(NO_ALLOC_FAIL_TESTS)'] }
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ This library allows you to do in-place initialization safely.
This library requires unstable features when the `alloc` or `std` features are enabled and thus
can only be used with a nightly compiler. The internally used features are:
- `allocator_api`
- `new_uninit`
- `get_mut_unchecked`

When enabling the `alloc` or `std` feature, the user will be required to activate these features:
Expand Down
4 changes: 0 additions & 4 deletions examples/big_struct_in_place.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
#![feature(allocator_api)]

use std::convert::Infallible;

use pinned_init::*;

// Struct with size over 1GiB
Expand Down
9 changes: 6 additions & 3 deletions examples/error.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
#![feature(allocator_api)]
#![cfg_attr(feature = "alloc", feature(allocator_api))]

use core::convert::Infallible;

#[cfg(feature = "alloc")]
use std::alloc::AllocError;

#[derive(Debug)]
pub struct Error;

impl From<Infallible> for Error {
fn from(_: Infallible) -> Self {
Self
fn from(e: Infallible) -> Self {
match e {}
Comment thread
bonzini marked this conversation as resolved.
}
}

#[cfg(feature = "alloc")]
impl From<AllocError> for Error {
fn from(_: AllocError) -> Self {
Self
Expand Down
29 changes: 29 additions & 0 deletions src/__internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,3 +261,32 @@ impl OnlyCallFromDrop {
Self(())
}
}

/// Initializer that always fails.
///
/// Used by [`assert_pinned!`].
///
/// [`assert_pinned!`]: crate::assert_pinned
pub struct AlwaysFail<T: ?Sized> {
_t: PhantomData<T>,
}

impl<T: ?Sized> AlwaysFail<T> {
/// Creates a new initializer that always fails.
pub fn new() -> Self {
Self { _t: PhantomData }
}
}

impl<T: ?Sized> Default for AlwaysFail<T> {
fn default() -> Self {
Self::new()
}
}

// SAFETY: `__pinned_init` always fails, which is always okay.
unsafe impl<T: ?Sized> PinInit<T, ()> for AlwaysFail<T> {
unsafe fn __pinned_init(self, _slot: *mut T) -> Result<(), ()> {
Err(())
}
}
172 changes: 136 additions & 36 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
//! This library requires unstable features when the `alloc` or `std` features are enabled and thus
//! can only be used with a nightly compiler. The internally used features are:
//! - `allocator_api`
//! - `new_uninit`
//! - `get_mut_unchecked`
//!
//! When enabling the `alloc` or `std` feature, the user will be required to activate these features:
Expand Down Expand Up @@ -237,7 +236,6 @@
#![forbid(missing_docs, unsafe_op_in_unsafe_fn)]
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(feature = "alloc", feature(allocator_api))]
#![cfg_attr(feature = "alloc", feature(new_uninit))]
#![cfg_attr(feature = "alloc", feature(get_mut_unchecked))]

#[cfg(feature = "alloc")]
Expand Down Expand Up @@ -747,6 +745,75 @@ macro_rules! try_init {
};
}

/// Asserts that a field on a struct using `#[pin_data]` is marked with `#[pin]` ie. that it is
/// structurally pinned.
///
/// # Example
///
/// This will succeed:
/// ```
/// use pinned_init::*;
/// #[pin_data]
/// struct MyStruct {
/// #[pin]
/// some_field: u64,
/// }
///
/// assert_pinned!(MyStruct, some_field, u64);
/// ```
///
/// This will fail:
// TODO: replace with `compile_fail` when supported.
/// ```ignore
/// # use pinned_init::*;
/// #[pin_data]
/// struct MyStruct {
/// some_field: u64,
/// }
///
/// assert_pinned!(MyStruct, some_field, u64);
/// ```
///
/// Some uses of the macro may trigger the `can't use generic parameters from outer item` error. To
/// work around this, you may pass the `inline` parameter to the macro. The `inline` parameter can
/// only be used when the macro is invoked from a function body.
/// ```
/// # use pinned_init::*;
/// # use core::pin::Pin;
/// #[pin_data]
/// struct Foo<T> {
/// #[pin]
/// elem: T,
/// }
///
/// impl<T> Foo<T> {
/// fn project(self: Pin<&mut Self>) -> Pin<&mut T> {
/// assert_pinned!(Foo<T>, elem, T, inline);
///
/// // SAFETY: The field is structurally pinned.
/// unsafe { self.map_unchecked_mut(|me| &mut me.elem) }
/// }
/// }
/// ```
#[macro_export]
macro_rules! assert_pinned {
($ty:ty, $field:ident, $field_ty:ty, inline) => {
let _ = move |ptr: *mut $field_ty| {
// SAFETY: This code is unreachable.
let data = unsafe { <$ty as $crate::__internal::HasPinData>::__pin_data() };
let init = $crate::__internal::AlwaysFail::<$field_ty>::new();
// SAFETY: This code is unreachable.
unsafe { data.$field(ptr, init) }.ok();
};
};

($ty:ty, $field:ident, $field_ty:ty) => {
const _: () = {
$crate::assert_pinned!($ty, $field, $field_ty, inline);
};
};
}

/// A pin-initializer for the type `T`.
///
/// To use this initializer, you will need a suitable memory location that can hold a `T`. This can
Expand Down Expand Up @@ -826,11 +893,8 @@ where
let val = unsafe { &mut *slot };
// SAFETY: `slot` is considered pinned.
let val = unsafe { Pin::new_unchecked(val) };
(self.1)(val).map_err(|e| {
// SAFETY: `slot` was initialized above.
unsafe { core::ptr::drop_in_place(slot) };
e
})
// SAFETY: `slot` was initialized above.
(self.1)(val).inspect_err(|_| unsafe { core::ptr::drop_in_place(slot) })
}
}

Expand Down Expand Up @@ -924,11 +988,9 @@ where
// SAFETY: All requirements fulfilled since this function is `__init`.
unsafe { self.0.__pinned_init(slot)? };
// SAFETY: The above call initialized `slot` and we still have unique access.
(self.1)(unsafe { &mut *slot }).map_err(|e| {
(self.1)(unsafe { &mut *slot }).inspect_err(|_|
// SAFETY: `slot` was initialized above.
unsafe { core::ptr::drop_in_place(slot) };
e
})
unsafe { core::ptr::drop_in_place(slot) })
}
}

Expand Down Expand Up @@ -1144,27 +1206,15 @@ impl<T> InPlaceInit<T> for Box<T> {
where
E: From<AllocError>,
{
let mut this = Box::try_new_uninit()?;
let slot = this.as_mut_ptr();
// SAFETY: When init errors/panics, slot will get deallocated but not dropped,
// slot is valid and will not be moved, because we pin it later.
unsafe { init.__pinned_init(slot)? };
// SAFETY: All fields have been initialized.
Ok(unsafe { this.assume_init() }.into())
Box::try_new_uninit()?.write_pin_init(init)
}

#[inline]
fn try_init<E>(init: impl Init<T, E>) -> Result<Self, E>
where
E: From<AllocError>,
{
let mut this = Box::try_new_uninit()?;
let slot = this.as_mut_ptr();
// SAFETY: When init errors/panics, slot will get deallocated but not dropped,
// slot is valid.
unsafe { init.__init(slot)? };
// SAFETY: All fields have been initialized.
Ok(unsafe { this.assume_init() })
Box::try_new_uninit()?.write_init(init)
}
}

Expand All @@ -1175,29 +1225,79 @@ impl<T> InPlaceInit<T> for Arc<T> {
where
E: From<AllocError>,
{
let mut this = Arc::try_new_uninit()?;
let slot = unsafe { Arc::get_mut_unchecked(&mut this) };
let slot = slot.as_mut_ptr();
// SAFETY: When init errors/panics, slot will get deallocated but not dropped,
// slot is valid and will not be moved, because we pin it later.
unsafe { init.__pinned_init(slot)? };
// SAFETY: All fields have been initialized and this is the only `Arc` to that data.
Ok(unsafe { Pin::new_unchecked(this.assume_init()) })
Arc::try_new_uninit()?.write_pin_init(init)
}

#[inline]
fn try_init<E>(init: impl Init<T, E>) -> Result<Self, E>
where
E: From<AllocError>,
{
let mut this = Arc::try_new_uninit()?;
let slot = unsafe { Arc::get_mut_unchecked(&mut this) };
Arc::try_new_uninit()?.write_init(init)
}
}

/// Smart pointer containing uninitialized memory and that can write a value.
pub trait InPlaceWrite<T> {
/// The type `Self` turns into when the contents are initialized.
type Initialized;

/// Use the given initializer to write a value into `self`.
///
/// Does not drop the current value and considers it as uninitialized memory.
fn write_init<E>(self, init: impl Init<T, E>) -> Result<Self::Initialized, E>;

/// Use the given pin-initializer to write a value into `self`.
///
/// Does not drop the current value and considers it as uninitialized memory.
fn write_pin_init<E>(self, init: impl PinInit<T, E>) -> Result<Pin<Self::Initialized>, E>;
}

#[cfg(feature = "alloc")]
impl<T> InPlaceWrite<T> for Box<MaybeUninit<T>> {
type Initialized = Box<T>;

fn write_init<E>(mut self, init: impl Init<T, E>) -> Result<Self::Initialized, E> {
let slot = self.as_mut_ptr();
// SAFETY: When init errors/panics, slot will get deallocated but not dropped,
// slot is valid.
unsafe { init.__init(slot)? };
// SAFETY: All fields have been initialized.
Ok(unsafe { self.assume_init() })
}

fn write_pin_init<E>(mut self, init: impl PinInit<T, E>) -> Result<Pin<Self::Initialized>, E> {
let slot = self.as_mut_ptr();
// SAFETY: When init errors/panics, slot will get deallocated but not dropped,
// slot is valid and will not be moved, because we pin it later.
unsafe { init.__pinned_init(slot)? };
// SAFETY: All fields have been initialized.
Ok(unsafe { self.assume_init() }.into())
}
}

#[cfg(feature = "alloc")]
impl<T> InPlaceWrite<T> for Arc<MaybeUninit<T>> {
type Initialized = Arc<T>;

fn write_init<E>(mut self, init: impl Init<T, E>) -> Result<Self::Initialized, E> {
let slot = unsafe { Arc::get_mut_unchecked(&mut self) };
let slot = slot.as_mut_ptr();
// SAFETY: When init errors/panics, slot will get deallocated but not dropped,
// slot is valid.
unsafe { init.__init(slot)? };
// SAFETY: All fields have been initialized.
Ok(unsafe { this.assume_init() })
Ok(unsafe { self.assume_init() })
}

fn write_pin_init<E>(mut self, init: impl PinInit<T, E>) -> Result<Pin<Self::Initialized>, E> {
let slot = unsafe { Arc::get_mut_unchecked(&mut self) };
let slot = slot.as_mut_ptr();
// SAFETY: When init errors/panics, slot will get deallocated but not dropped,
// slot is valid and will not be moved, because we pin it later.
unsafe { init.__pinned_init(slot)? };
// SAFETY: All fields have been initialized and this is the only `Arc` to that data.
Ok(unsafe { Pin::new_unchecked(self.assume_init()) })
}
}

Expand Down
Loading