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 Oct 1, 2020
1 parent fc580bc commit 4835493
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 11 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
123 changes: 117 additions & 6 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, matches! requires Rust 1.42
Expand Down Expand Up @@ -197,6 +191,7 @@ macro_rules! __pin_project_internal {
$field_vis:vis $field:ident: $field_ty:ty
),+
}
$(impl $($pinned_drop:tt)*)?
) => {
$(#[$attrs])*
$vis struct $ident $($def_generics)*
Expand Down Expand Up @@ -271,6 +266,7 @@ macro_rules! __pin_project_internal {
$crate::__pin_project_internal! { @make_drop_impl;
[$ident]
[$($impl_generics)*] [$($ty_generics)*] [$(where $($where_clause)*)?]
$(impl $($pinned_drop)*)?
}

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

// =============================================================================================
// make_drop_impl
(@make_drop_impl;
// FIXME: check $_ident + $_ty_generics == $self_ty
[$_ident:ident]
[$($_impl_generics:tt)*] [$($_ty_generics:tt)*] [$(where $($_where_clause:tt)* )?]
impl $(<
$( $generics:tt
$(: $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 $(<
$( $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 $(<
$( $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 $(<
$( $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;
[$ident:ident]
[$($impl_generics:tt)*] [$($ty_generics:tt)*] [$(where $($where_clause:tt)* )?]
Expand Down Expand Up @@ -482,6 +571,7 @@ macro_rules! __pin_project_internal {
$field_vis:vis $field:ident: $field_ty:ty
),+ $(,)?
}
$(impl $($pinned_drop:tt)*)?
) => {
$crate::__pin_project_internal! { @struct_internal;
[pub(crate)]
Expand Down Expand Up @@ -515,6 +605,7 @@ macro_rules! __pin_project_internal {
$field_vis $field: $field_ty
),+
}
$(impl $($pinned_drop)*)?
}
};
(
Expand All @@ -541,6 +632,7 @@ macro_rules! __pin_project_internal {
$field_vis:vis $field:ident: $field_ty:ty
),+ $(,)?
}
$(impl $($pinned_drop:tt)*)?
) => {
$crate::__pin_project_internal! { @struct_internal;
[$vis]
Expand Down Expand Up @@ -574,6 +666,7 @@ macro_rules! __pin_project_internal {
$field_vis $field: $field_ty
),+
}
$(impl $($pinned_drop)*)?
}
};
}
Expand All @@ -588,6 +681,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
89 changes: 89 additions & 0 deletions tests/expand/tests/expand/pinned_drop-struct.expanded.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use pin_project_lite::pin_project;
use std::pin::Pin;
struct Struct<T, U> {
pinned: T,
unpinned: U,
}
#[allow(single_use_lifetimes)]
#[allow(clippy::used_underscore_binding)]
const _: () = {
#[allow(dead_code)]
#[allow(clippy::mut_mut)]
#[allow(clippy::type_repetition_in_bounds)]
struct Projection<'__pin, T, U>
where
Struct<T, U>: '__pin,
{
pinned: ::pin_project_lite::__private::Pin<&'__pin mut (T)>,
unpinned: &'__pin mut (U),
}
#[allow(dead_code)]
#[allow(clippy::type_repetition_in_bounds)]
struct ProjectionRef<'__pin, T, U>
where
Struct<T, U>: '__pin,
{
pinned: ::pin_project_lite::__private::Pin<&'__pin (T)>,
unpinned: &'__pin (U),
}
impl<T, U> Struct<T, U> {
fn project<'__pin>(
self: ::pin_project_lite::__private::Pin<&'__pin mut Self>,
) -> Projection<'__pin, T, U> {
unsafe {
let Self { pinned, unpinned } = self.get_unchecked_mut();
Projection {
pinned: ::pin_project_lite::__private::Pin::new_unchecked(pinned),
unpinned: unpinned,
}
}
}
fn project_ref<'__pin>(
self: ::pin_project_lite::__private::Pin<&'__pin Self>,
) -> ProjectionRef<'__pin, T, U> {
unsafe {
let Self { pinned, unpinned } = self.get_ref();
ProjectionRef {
pinned: ::pin_project_lite::__private::Pin::new_unchecked(pinned),
unpinned: unpinned,
}
}
}
}
struct __Origin<'__pin, T, U> {
__dummy_lifetime: ::pin_project_lite::__private::PhantomData<&'__pin ()>,
pinned: T,
unpinned: ::pin_project_lite::__private::AlwaysUnpin<U>,
}
impl<'__pin, T, U> ::pin_project_lite::__private::Unpin for Struct<T, U> where
__Origin<'__pin, T, U>: ::pin_project_lite::__private::Unpin
{
}
impl<T, U> ::pin_project_lite::__private::Drop for Struct<T, U> {
fn drop(&mut self) {
let pinned_self = unsafe { ::pin_project_lite::__private::Pin::new_unchecked(self) };
unsafe {
::pin_project_lite::__private::PinnedDrop::drop(pinned_self);
}
}
}
impl<T, U> ::pin_project_lite::__private::PinnedDrop for Struct<T, U> {
unsafe fn drop(self: ::pin_project_lite::__private::Pin<&mut Self>) {
trait __InnerDrop {
fn __drop_inner(self: ::pin_project_lite::__private::Pin<&mut Self>);
}
impl<T, U> __InnerDrop for Struct<T, U> {
fn __drop_inner(self: ::pin_project_lite::__private::Pin<&mut Self>) {
let _this = self;
}
}
__InnerDrop::__drop_inner(self);
}
}
#[deny(safe_packed_borrows)]
fn __assert_not_repr_packed<T, U>(this: &Struct<T, U>) {
&this.pinned;
&this.unpinned;
}
};
fn main() {}
17 changes: 17 additions & 0 deletions tests/expand/tests/expand/pinned_drop-struct.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use pin_project_lite::pin_project;
use std::pin::Pin;

pin_project! {
struct Struct<T, U> {
#[pin]
pinned: T,
unpinned: U,
}
impl<T, U> PinnedDrop for Struct<T, U> {
fn drop(self: Pin<&mut Self>) {
let _this = self;
}
}
}

fn main() {}
20 changes: 20 additions & 0 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -432,3 +432,23 @@ fn trailing_comma() {
// }
// }
}

#[test]
fn pinned_drop() {
pin_project! {
pub struct Struct<'a> {
was_dropped: &'a mut bool,
#[pin]
field: u8,
}
impl PinnedDrop for Struct<'_> {
fn drop(self: Pin<&mut Self>) {
**self.project().was_dropped = true;
}
}
}

let mut was_dropped = false;
drop(Struct { was_dropped: &mut was_dropped, field: 42 });
assert!(was_dropped);
}

0 comments on commit 4835493

Please sign in to comment.