Skip to content

Commit

Permalink
Support custom Drop implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
taiki-e committed Jun 26, 2020
1 parent efcc085 commit 91284cb
Show file tree
Hide file tree
Showing 5 changed files with 314 additions and 40 deletions.
5 changes: 0 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,6 @@ This macro does not handle any invalid input. So error messages are not to be us

pin-project-lite will refuse anything other than a braced struct with named fields. Enums and tuple structs are not supported.

### Different: No support for custom Drop implementation

pin-project supports this by [`#[pinned_drop]`][pinned-drop].

### Different: No support for custom Unpin implementation

pin-project supports this by [`UnsafeUnpin`][unsafe-unpin] and [`!Unpin`][not-unpin].
Expand All @@ -92,7 +88,6 @@ pin-project supports this by [`UnsafeUnpin`][unsafe-unpin] and [`!Unpin`][not-un
[naming]: https://docs.rs/pin-project/0.4/pin_project/attr.pin_project.html
[not-unpin]: https://docs.rs/pin-project/0.4/pin_project/attr.pin_project.html#unpin
[pin-project]: https://github.com/taiki-e/pin-project
[pinned-drop]: https://docs.rs/pin-project/0.4/pin_project/attr.pin_project.html#pinned_drop
[unsafe-unpin]: https://docs.rs/pin-project/0.4/pin_project/attr.pin_project.html#unsafeunpin

## License
Expand Down
223 changes: 188 additions & 35 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,6 @@
//!
//! pin-project-lite will refuse anything other than a braced struct with named fields. Enums and tuple structs are not supported.
//!
//! ## Different: No support for custom Drop implementation
//!
//! pin-project supports this by [`#[pinned_drop]`][pinned-drop].
//!
//! ## Different: No support for custom Unpin implementation
//!
//! pin-project supports this by [`UnsafeUnpin`][unsafe-unpin] and [`!Unpin`][not-unpin].
Expand All @@ -65,7 +61,6 @@
//! [naming]: https://docs.rs/pin-project/0.4/pin_project/attr.pin_project.html
//! [not-unpin]: https://docs.rs/pin-project/0.4/pin_project/attr.pin_project.html#unpin
//! [pin-project]: https://github.com/taiki-e/pin-project
//! [pinned-drop]: https://docs.rs/pin-project/0.4/pin_project/attr.pin_project.html#pinned_drop
//! [unsafe-unpin]: https://docs.rs/pin-project/0.4/pin_project/attr.pin_project.html#unsafeunpin

#![no_std]
Expand All @@ -74,7 +69,6 @@
no_crate_inject,
attr(deny(warnings, rust_2018_idioms, single_use_lifetimes), allow(dead_code))
))]
#![warn(unsafe_code)]
#![warn(rust_2018_idioms, single_use_lifetimes, unreachable_pub)]
#![warn(clippy::all, clippy::default_trait_access)]
// mem::take and #[non_exhaustive] requires Rust 1.40
Expand Down Expand Up @@ -204,6 +198,7 @@ macro_rules! __pin_project_internal {
$field_vis:vis $field:ident: $field_ty:ty
),+ $(,)?
}
$(impl $($pinned_drop:tt)*)?
) => {
$crate::__pin_project_internal! { @internal (pub(crate))
$(#[$attrs])*
Expand All @@ -229,6 +224,7 @@ macro_rules! __pin_project_internal {
$field_vis $field: $field_ty
),+
}
$(impl $($pinned_drop)*)?
}
};
(
Expand All @@ -255,6 +251,7 @@ macro_rules! __pin_project_internal {
$field_vis:vis $field:ident: $field_ty:ty
),+ $(,)?
}
$(impl $($pinned_drop:tt)*)?
) => {
$crate::__pin_project_internal! { @internal ($vis)
$(#[$attrs])*
Expand All @@ -280,6 +277,7 @@ macro_rules! __pin_project_internal {
$field_vis $field: $field_ty
),+
}
$(impl $($pinned_drop)*)?
}
};

Expand Down Expand Up @@ -307,6 +305,7 @@ macro_rules! __pin_project_internal {
$field_vis:vis $field:ident: $field_ty:ty
),+
}
$(impl $($pinned_drop:tt)*)?
) => {
$(#[$attrs])*
$vis struct $ident $(<
Expand Down Expand Up @@ -475,35 +474,24 @@ macro_rules! __pin_project_internal {
{
}

// Ensure that struct does not implement `Drop`.
//
// There are two possible cases:
// 1. The user type does not implement Drop. In this case,
// the first blanked impl will not apply to it. This code
// will compile, as there is only one impl of MustNotImplDrop for the user type
// 2. The user type does impl Drop. This will make the blanket impl applicable,
// which will then comflict with the explicit MustNotImplDrop impl below.
// This will result in a compilation error, which is exactly what we want.
trait MustNotImplDrop {}
#[allow(clippy::drop_bounds)]
impl<T: $crate::__private::Drop> MustNotImplDrop for T {}
impl $(<
$( $lifetime $(: $lifetime_bound)? ,)*
$( $generics
$(: $generics_bound)?
$(: ?$generics_unsized_bound)?
$(: $generics_lifetime_bound)?
),*
>)?
MustNotImplDrop for $ident $(< $($lifetime,)* $($generics),* >)?
$(where
$( $where_clause_ty
$(: $where_clause_bound)?
$(: ?$where_clause_unsized_bound)?
$(: $where_clause_lifetime_bound)?
),*
)?
{
$crate::__pin_project_internal! { @make_drop_impl
[$(<
$( $lifetime $(: $lifetime_bound)? ,)*
$( $generics
$(: $generics_bound)?
$(: ?$generics_unsized_bound)?
$(: $generics_lifetime_bound)?
),*
>)?]
[$ident]
[$(where
$( $where_clause_ty
$(: $where_clause_bound)?
$(: ?$where_clause_unsized_bound)?
$(: $where_clause_lifetime_bound)?
),*
)?]
$(impl $($pinned_drop)*)?
}

// Ensure that it's impossible to use pin projections on a #[repr(packed)] struct.
Expand Down Expand Up @@ -678,6 +666,153 @@ macro_rules! __pin_project_internal {
}
};

(@make_drop_impl
[$($_impl_generics:tt)*]
[$_ident:ident]
[$($_where_clause:tt)*]
impl $(<
$( $lifetime:lifetime $(: $lifetime_bound:lifetime)? ),*
$( $generics:ident
$(: $generics_bound:path)?
$(: ?$generics_unsized_bound:path)?
$(: $generics_lifetime_bound:lifetime)?
),*
>)? PinnedDrop for $self_ty:ty
$(where
$( $where_clause_ty:ty
$(: $where_clause_bound:path)?
$(: ?$where_clause_unsized_bound:path)?
$(: $where_clause_lifetime_bound:lifetime)?
),*
)?
{
fn drop($(mut $self1:ident)? $($self2:ident)?: Pin<&mut Self>) {
$($tt:tt)*
}
}
) => {
impl $(<
$( $lifetime $(: $lifetime_bound)? ),*
$( $generics
$(: $generics_bound)?
$(: ?$generics_unsized_bound)?
$(: $generics_lifetime_bound)?
),*
>)? $crate::__private::Drop for $self_ty
$(where
$( $where_clause_ty
$(: $where_clause_bound)?
$(: ?$where_clause_unsized_bound)?
$(: $where_clause_lifetime_bound)?
),*
)?
{
fn drop(&mut self) {
// Safety - we're in 'drop', so we know that 'self' will
// never move again.
let pinned_self = unsafe { $crate::__private::Pin::new_unchecked(self) };
// We call `pinned_drop` only once. Since `PinnedDrop::drop`
// is an unsafe method and a private API, it is never called again in safe
// code *unless the user uses a maliciously crafted macro*.
unsafe {
$crate::__private::PinnedDrop::drop(pinned_self);
}
}
}
impl $(<
$( $lifetime $(: $lifetime_bound)? ),*
$( $generics
$(: $generics_bound)?
$(: ?$generics_unsized_bound)?
$(: $generics_lifetime_bound)?
),*
>)? $crate::__private::PinnedDrop for $self_ty
$(where
$( $where_clause_ty
$(: $where_clause_bound)?
$(: ?$where_clause_unsized_bound)?
$(: $where_clause_lifetime_bound)?
),*
)?
{
unsafe fn drop(self: $crate::__private::Pin<&mut Self>) {
trait __InnerDrop {
fn __drop_inner(self: $crate::__private::Pin<&mut Self>);
}
impl $(<
$( $lifetime $(: $lifetime_bound)? ),*
$( $generics
$(: $generics_bound)?
$(: ?$generics_unsized_bound)?
$(: $generics_lifetime_bound)?
),*
>)? __InnerDrop for $self_ty
$(where
$( $where_clause_ty
$(: $where_clause_bound)?
$(: ?$where_clause_unsized_bound)?
$(: $where_clause_lifetime_bound)?
),*
)?
{
fn __drop_inner($(mut $self1)? $($self2)?: $crate::__private::Pin<&mut Self>) {
$($tt)*
}
}
__InnerDrop::__drop_inner(self);
}
}
};
(@make_drop_impl
[$(<
$( $lifetime:lifetime $(: $lifetime_bound:lifetime)? ,)*
$( $generics:ident
$(: $generics_bound:path)?
$(: ?$generics_unsized_bound:path)?
$(: $generics_lifetime_bound:lifetime)?
),*
>)?]
[$ident:ident]
[$(where
$( $where_clause_ty:ty
$(: $where_clause_bound:path)?
$(: ?$where_clause_unsized_bound:path)?
$(: $where_clause_lifetime_bound:lifetime)?
),*
)?]
) => {
// Ensure that struct does not implement `Drop`.
//
// There are two possible cases:
// 1. The user type does not implement Drop. In this case,
// the first blanked impl will not apply to it. This code
// will compile, as there is only one impl of MustNotImplDrop for the user type
// 2. The user type does impl Drop. This will make the blanket impl applicable,
// which will then comflict with the explicit MustNotImplDrop impl below.
// This will result in a compilation error, which is exactly what we want.
trait MustNotImplDrop {}
#[allow(clippy::drop_bounds)]
impl<T: $crate::__private::Drop> MustNotImplDrop for T {}
impl $(<
$( $lifetime $(: $lifetime_bound)? ,)*
$( $generics
$(: $generics_bound)?
$(: ?$generics_unsized_bound)?
$(: $generics_lifetime_bound)?
),*
>)?
MustNotImplDrop for $ident $(< $($lifetime,)* $($generics),* >)?
$(where
$( $where_clause_ty
$(: $where_clause_bound)?
$(: ?$where_clause_unsized_bound)?
$(: $where_clause_lifetime_bound)?
),*
)?
{
}
};

// make_unpin_bound
(@make_unpin_bound
#[pin]
Expand Down Expand Up @@ -736,6 +871,24 @@ pub mod __private {
pin::Pin,
};

// Implementing `PinnedDrop::drop` is safe, but calling it is not safe.
// This is because destructors can be called multiple times in safe code and
// [double dropping is unsound](https://github.com/rust-lang/rust/pull/62360).
//
// Ideally, it would be desirable to be able to forbid manual calls in
// the same way as [`Drop::drop`], but the library cannot do it. So, by using
// macros and replacing them with private traits, we prevent users from
// calling `PinnedDrop::drop`.
//
// Users can implement [`Drop`] safely using `#[pinned_drop]` and can drop a
// type that implements `PinnedDrop` using the [`drop`] function safely.
// **Do not call or implement this trait directly.**
#[doc(hidden)]
pub trait PinnedDrop {
#[doc(hidden)]
unsafe fn drop(self: Pin<&mut Self>);
}

// This is an internal helper struct used by `pin_project!`.
#[doc(hidden)]
pub struct AlwaysUnpin<T: ?Sized>(PhantomData<T>);
Expand Down
Loading

0 comments on commit 91284cb

Please sign in to comment.