UnsafePinned: implement opsem effects of UnsafeUnpin
This commit is contained in:
parent
71dc761bfe
commit
590c1c9966
11 changed files with 113 additions and 31 deletions
|
|
@ -1694,6 +1694,10 @@ rustc_queries! {
|
||||||
query is_freeze_raw(env: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool {
|
query is_freeze_raw(env: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool {
|
||||||
desc { "computing whether `{}` is freeze", env.value }
|
desc { "computing whether `{}` is freeze", env.value }
|
||||||
}
|
}
|
||||||
|
/// Query backing `Ty::is_unsafe_unpin`.
|
||||||
|
query is_unsafe_unpin_raw(env: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool {
|
||||||
|
desc { "computing whether `{}` is `UnsafeUnpin`", env.value }
|
||||||
|
}
|
||||||
/// Query backing `Ty::is_unpin`.
|
/// Query backing `Ty::is_unpin`.
|
||||||
query is_unpin_raw(env: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool {
|
query is_unpin_raw(env: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool {
|
||||||
desc { "computing whether `{}` is `Unpin`", env.value }
|
desc { "computing whether `{}` is `Unpin`", env.value }
|
||||||
|
|
|
||||||
|
|
@ -1049,9 +1049,11 @@ where
|
||||||
hir::Mutability::Not => {
|
hir::Mutability::Not => {
|
||||||
PointerKind::SharedRef { frozen: optimize && ty.is_freeze(tcx, typing_env) }
|
PointerKind::SharedRef { frozen: optimize && ty.is_freeze(tcx, typing_env) }
|
||||||
}
|
}
|
||||||
hir::Mutability::Mut => {
|
hir::Mutability::Mut => PointerKind::MutableRef {
|
||||||
PointerKind::MutableRef { unpin: optimize && ty.is_unpin(tcx, typing_env) }
|
unpin: optimize
|
||||||
}
|
&& ty.is_unpin(tcx, typing_env)
|
||||||
|
&& ty.is_unsafe_unpin(tcx, typing_env),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
tcx.layout_of(typing_env.as_query_input(ty)).ok().map(|layout| PointeeInfo {
|
tcx.layout_of(typing_env.as_query_input(ty)).ok().map(|layout| PointeeInfo {
|
||||||
|
|
@ -1146,7 +1148,9 @@ where
|
||||||
debug_assert!(pointee.safe.is_none());
|
debug_assert!(pointee.safe.is_none());
|
||||||
let optimize = tcx.sess.opts.optimize != OptLevel::No;
|
let optimize = tcx.sess.opts.optimize != OptLevel::No;
|
||||||
pointee.safe = Some(PointerKind::Box {
|
pointee.safe = Some(PointerKind::Box {
|
||||||
unpin: optimize && boxed_ty.is_unpin(tcx, typing_env),
|
unpin: optimize
|
||||||
|
&& boxed_ty.is_unpin(tcx, typing_env)
|
||||||
|
&& boxed_ty.is_unsafe_unpin(tcx, typing_env),
|
||||||
global: this.ty.is_box_global(tcx),
|
global: this.ty.is_box_global(tcx),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1189,14 +1189,23 @@ impl<'tcx> Ty<'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks whether values of this type `T` implement the `UnsafeUnpin` trait.
|
||||||
|
pub fn is_unsafe_unpin(self, tcx: TyCtxt<'tcx>, typing_env: ty::TypingEnv<'tcx>) -> bool {
|
||||||
|
self.is_trivially_unpin() || tcx.is_unsafe_unpin_raw(typing_env.as_query_input(self))
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks whether values of this type `T` implement the `Unpin` trait.
|
/// Checks whether values of this type `T` implement the `Unpin` trait.
|
||||||
|
///
|
||||||
|
/// Note that this is a safe trait, so it cannot be very semantically meaningful.
|
||||||
|
/// However, as a hack to mitigate <https://github.com/rust-lang/rust/issues/63818> until a
|
||||||
|
/// proper solution is implemented, we do give special semantics to the `Unpin` trait.
|
||||||
pub fn is_unpin(self, tcx: TyCtxt<'tcx>, typing_env: ty::TypingEnv<'tcx>) -> bool {
|
pub fn is_unpin(self, tcx: TyCtxt<'tcx>, typing_env: ty::TypingEnv<'tcx>) -> bool {
|
||||||
self.is_trivially_unpin() || tcx.is_unpin_raw(typing_env.as_query_input(self))
|
self.is_trivially_unpin() || tcx.is_unpin_raw(typing_env.as_query_input(self))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fast path helper for testing if a type is `Unpin`.
|
/// Fast path helper for testing if a type is `Unpin` *and* `UnsafeUnpin`.
|
||||||
///
|
///
|
||||||
/// Returning true means the type is known to be `Unpin`. Returning
|
/// Returning true means the type is known to be `Unpin` and `UnsafeUnpin`. Returning
|
||||||
/// `false` means nothing -- could be `Unpin`, might not be.
|
/// `false` means nothing -- could be `Unpin`, might not be.
|
||||||
fn is_trivially_unpin(self) -> bool {
|
fn is_trivially_unpin(self) -> bool {
|
||||||
match self.kind() {
|
match self.kind() {
|
||||||
|
|
|
||||||
|
|
@ -306,8 +306,12 @@ fn arg_attrs_for_rust_scalar<'tcx>(
|
||||||
let kind = if let Some(kind) = pointee.safe {
|
let kind = if let Some(kind) = pointee.safe {
|
||||||
Some(kind)
|
Some(kind)
|
||||||
} else if let Some(pointee) = drop_target_pointee {
|
} else if let Some(pointee) = drop_target_pointee {
|
||||||
|
assert_eq!(pointee, layout.ty.builtin_deref(true).unwrap());
|
||||||
|
assert_eq!(offset, Size::ZERO);
|
||||||
// The argument to `drop_in_place` is semantically equivalent to a mutable reference.
|
// The argument to `drop_in_place` is semantically equivalent to a mutable reference.
|
||||||
Some(PointerKind::MutableRef { unpin: pointee.is_unpin(tcx, cx.typing_env) })
|
let mutref = Ty::new_mut_ref(tcx, tcx.lifetimes.re_erased, pointee);
|
||||||
|
let layout = cx.layout_of(mutref).unwrap();
|
||||||
|
layout.pointee_info_at(&cx, offset).and_then(|pi| pi.safe)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -8,36 +8,43 @@ use rustc_span::DUMMY_SP;
|
||||||
use rustc_trait_selection::traits;
|
use rustc_trait_selection::traits;
|
||||||
|
|
||||||
fn is_copy_raw<'tcx>(tcx: TyCtxt<'tcx>, query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool {
|
fn is_copy_raw<'tcx>(tcx: TyCtxt<'tcx>, query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool {
|
||||||
is_item_raw(tcx, query, LangItem::Copy)
|
is_trait_raw(tcx, query, LangItem::Copy)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_use_cloned_raw<'tcx>(
|
fn is_use_cloned_raw<'tcx>(
|
||||||
tcx: TyCtxt<'tcx>,
|
tcx: TyCtxt<'tcx>,
|
||||||
query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>,
|
query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
is_item_raw(tcx, query, LangItem::UseCloned)
|
is_trait_raw(tcx, query, LangItem::UseCloned)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_sized_raw<'tcx>(tcx: TyCtxt<'tcx>, query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool {
|
fn is_sized_raw<'tcx>(tcx: TyCtxt<'tcx>, query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool {
|
||||||
is_item_raw(tcx, query, LangItem::Sized)
|
is_trait_raw(tcx, query, LangItem::Sized)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_freeze_raw<'tcx>(tcx: TyCtxt<'tcx>, query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool {
|
fn is_freeze_raw<'tcx>(tcx: TyCtxt<'tcx>, query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool {
|
||||||
is_item_raw(tcx, query, LangItem::Freeze)
|
is_trait_raw(tcx, query, LangItem::Freeze)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_unsafe_unpin_raw<'tcx>(
|
||||||
|
tcx: TyCtxt<'tcx>,
|
||||||
|
query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>,
|
||||||
|
) -> bool {
|
||||||
|
is_trait_raw(tcx, query, LangItem::UnsafeUnpin)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_unpin_raw<'tcx>(tcx: TyCtxt<'tcx>, query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool {
|
fn is_unpin_raw<'tcx>(tcx: TyCtxt<'tcx>, query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>) -> bool {
|
||||||
is_item_raw(tcx, query, LangItem::Unpin)
|
is_trait_raw(tcx, query, LangItem::Unpin)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_async_drop_raw<'tcx>(
|
fn is_async_drop_raw<'tcx>(
|
||||||
tcx: TyCtxt<'tcx>,
|
tcx: TyCtxt<'tcx>,
|
||||||
query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>,
|
query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
is_item_raw(tcx, query, LangItem::AsyncDrop)
|
is_trait_raw(tcx, query, LangItem::AsyncDrop)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_item_raw<'tcx>(
|
fn is_trait_raw<'tcx>(
|
||||||
tcx: TyCtxt<'tcx>,
|
tcx: TyCtxt<'tcx>,
|
||||||
query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>,
|
query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>,
|
||||||
item: LangItem,
|
item: LangItem,
|
||||||
|
|
@ -53,6 +60,7 @@ pub(crate) fn provide(providers: &mut Providers) {
|
||||||
is_use_cloned_raw,
|
is_use_cloned_raw,
|
||||||
is_sized_raw,
|
is_sized_raw,
|
||||||
is_freeze_raw,
|
is_freeze_raw,
|
||||||
|
is_unsafe_unpin_raw,
|
||||||
is_unpin_raw,
|
is_unpin_raw,
|
||||||
is_async_drop_raw,
|
is_async_drop_raw,
|
||||||
..*providers
|
..*providers
|
||||||
|
|
|
||||||
|
|
@ -927,14 +927,20 @@ marker_impls! {
|
||||||
/// This is part of [RFC 3467](https://rust-lang.github.io/rfcs/3467-unsafe-pinned.html), and is
|
/// This is part of [RFC 3467](https://rust-lang.github.io/rfcs/3467-unsafe-pinned.html), and is
|
||||||
/// tracked by [#125735](https://github.com/rust-lang/rust/issues/125735).
|
/// tracked by [#125735](https://github.com/rust-lang/rust/issues/125735).
|
||||||
#[lang = "unsafe_unpin"]
|
#[lang = "unsafe_unpin"]
|
||||||
pub(crate) unsafe auto trait UnsafeUnpin {}
|
#[unstable(feature = "unsafe_unpin", issue = "125735")]
|
||||||
|
pub unsafe auto trait UnsafeUnpin {}
|
||||||
|
|
||||||
|
#[unstable(feature = "unsafe_unpin", issue = "125735")]
|
||||||
impl<T: ?Sized> !UnsafeUnpin for UnsafePinned<T> {}
|
impl<T: ?Sized> !UnsafeUnpin for UnsafePinned<T> {}
|
||||||
unsafe impl<T: ?Sized> UnsafeUnpin for PhantomData<T> {}
|
marker_impls! {
|
||||||
unsafe impl<T: ?Sized> UnsafeUnpin for *const T {}
|
#[unstable(feature = "unsafe_unpin", issue = "125735")]
|
||||||
unsafe impl<T: ?Sized> UnsafeUnpin for *mut T {}
|
unsafe UnsafeUnpin for
|
||||||
unsafe impl<T: ?Sized> UnsafeUnpin for &T {}
|
{T: ?Sized} PhantomData<T>,
|
||||||
unsafe impl<T: ?Sized> UnsafeUnpin for &mut T {}
|
{T: ?Sized} *const T,
|
||||||
|
{T: ?Sized} *mut T,
|
||||||
|
{T: ?Sized} &T,
|
||||||
|
{T: ?Sized} &mut T,
|
||||||
|
}
|
||||||
|
|
||||||
/// Types that do not require any pinning guarantees.
|
/// Types that do not require any pinning guarantees.
|
||||||
///
|
///
|
||||||
|
|
@ -1027,6 +1033,7 @@ impl !Unpin for PhantomPinned {}
|
||||||
// continue working. Ideally PhantomPinned could just wrap an `UnsafePinned<()>` to get the same
|
// continue working. Ideally PhantomPinned could just wrap an `UnsafePinned<()>` to get the same
|
||||||
// effect, but we can't add a new field to an already stable unit struct -- that would be a breaking
|
// effect, but we can't add a new field to an already stable unit struct -- that would be a breaking
|
||||||
// change.
|
// change.
|
||||||
|
#[unstable(feature = "unsafe_unpin", issue = "125735")]
|
||||||
impl !UnsafeUnpin for PhantomPinned {}
|
impl !UnsafeUnpin for PhantomPinned {}
|
||||||
|
|
||||||
marker_impls! {
|
marker_impls! {
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,9 @@ impl NewPermission {
|
||||||
access: None,
|
access: None,
|
||||||
protector: None,
|
protector: None,
|
||||||
}
|
}
|
||||||
} else if pointee.is_unpin(*cx.tcx, cx.typing_env()) {
|
} else if pointee.is_unpin(*cx.tcx, cx.typing_env())
|
||||||
|
&& pointee.is_unsafe_unpin(*cx.tcx, cx.typing_env())
|
||||||
|
{
|
||||||
// A regular full mutable reference. On `FnEntry` this is `noalias` and `dereferenceable`.
|
// A regular full mutable reference. On `FnEntry` this is `noalias` and `dereferenceable`.
|
||||||
NewPermission::Uniform {
|
NewPermission::Uniform {
|
||||||
perm: Permission::Unique,
|
perm: Permission::Unique,
|
||||||
|
|
@ -129,7 +131,9 @@ impl NewPermission {
|
||||||
fn from_box_ty<'tcx>(ty: Ty<'tcx>, kind: RetagKind, cx: &crate::MiriInterpCx<'tcx>) -> Self {
|
fn from_box_ty<'tcx>(ty: Ty<'tcx>, kind: RetagKind, cx: &crate::MiriInterpCx<'tcx>) -> Self {
|
||||||
// `ty` is not the `Box` but the field of the Box with this pointer (due to allocator handling).
|
// `ty` is not the `Box` but the field of the Box with this pointer (due to allocator handling).
|
||||||
let pointee = ty.builtin_deref(true).unwrap();
|
let pointee = ty.builtin_deref(true).unwrap();
|
||||||
if pointee.is_unpin(*cx.tcx, cx.typing_env()) {
|
if pointee.is_unpin(*cx.tcx, cx.typing_env())
|
||||||
|
&& pointee.is_unsafe_unpin(*cx.tcx, cx.typing_env())
|
||||||
|
{
|
||||||
// A regular box. On `FnEntry` this is `noalias`, but not `dereferenceable` (hence only
|
// A regular box. On `FnEntry` this is `noalias`, but not `dereferenceable` (hence only
|
||||||
// a weak protector).
|
// a weak protector).
|
||||||
NewPermission::Uniform {
|
NewPermission::Uniform {
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,8 @@ impl<'tcx> NewPermission {
|
||||||
retag_kind: RetagKind,
|
retag_kind: RetagKind,
|
||||||
cx: &crate::MiriInterpCx<'tcx>,
|
cx: &crate::MiriInterpCx<'tcx>,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
let ty_is_unpin = pointee.is_unpin(*cx.tcx, cx.typing_env());
|
let ty_is_unpin = pointee.is_unpin(*cx.tcx, cx.typing_env())
|
||||||
|
&& pointee.is_unsafe_unpin(*cx.tcx, cx.typing_env());
|
||||||
let ty_is_freeze = pointee.is_freeze(*cx.tcx, cx.typing_env());
|
let ty_is_freeze = pointee.is_freeze(*cx.tcx, cx.typing_env());
|
||||||
let is_protected = retag_kind == RetagKind::FnEntry;
|
let is_protected = retag_kind == RetagKind::FnEntry;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,36 @@ fn mutate(x: &UnsafePinned<i32>) {
|
||||||
unsafe { ptr.write(42) };
|
unsafe { ptr.write(42) };
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn mut_alias(x: &mut UnsafePinned<i32>, y: &mut UnsafePinned<i32>) {
|
||||||
let x = UnsafePinned::new(0);
|
unsafe {
|
||||||
mutate(&x);
|
x.get().write(0);
|
||||||
assert_eq!(x.into_inner(), 42);
|
y.get().write(0);
|
||||||
|
x.get().write(0);
|
||||||
|
y.get().write(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also try this with a type for which we implement `Unpin`, just to be extra mean.
|
||||||
|
struct MyUnsafePinned<T>(UnsafePinned<T>);
|
||||||
|
impl<T> Unpin for MyUnsafePinned<T> {}
|
||||||
|
|
||||||
|
fn my_mut_alias(x: &mut MyUnsafePinned<i32>, y: &mut MyUnsafePinned<i32>) {
|
||||||
|
unsafe {
|
||||||
|
x.0.get().write(0);
|
||||||
|
y.0.get().write(0);
|
||||||
|
x.0.get().write(0);
|
||||||
|
y.0.get().write(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut x = UnsafePinned::new(0i32);
|
||||||
|
mutate(&x);
|
||||||
|
assert_eq!(unsafe { x.get().read() }, 42);
|
||||||
|
|
||||||
|
let ptr = &raw mut x;
|
||||||
|
unsafe { mut_alias(&mut *ptr, &mut *ptr) };
|
||||||
|
|
||||||
|
let ptr = ptr.cast::<MyUnsafePinned<i32>>();
|
||||||
|
unsafe { my_mut_alias(&mut *ptr, &mut *ptr) };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,9 @@ pub trait BikeshedGuaranteedNoDrop {}
|
||||||
#[lang = "freeze"]
|
#[lang = "freeze"]
|
||||||
pub unsafe auto trait Freeze {}
|
pub unsafe auto trait Freeze {}
|
||||||
|
|
||||||
|
#[lang = "unsafe_unpin"]
|
||||||
|
pub unsafe auto trait UnsafeUnpin {}
|
||||||
|
|
||||||
#[lang = "unpin"]
|
#[lang = "unpin"]
|
||||||
#[diagnostic::on_unimplemented(
|
#[diagnostic::on_unimplemented(
|
||||||
note = "consider using the `pin!` macro\nconsider using `Box::pin` if you need to access the pinned value outside of the current scope",
|
note = "consider using the `pin!` macro\nconsider using `Box::pin` if you need to access the pinned value outside of the current scope",
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
//@ compile-flags: -Copt-level=3 -C no-prepopulate-passes
|
//@ compile-flags: -Copt-level=3 -C no-prepopulate-passes
|
||||||
#![crate_type = "lib"]
|
#![crate_type = "lib"]
|
||||||
#![feature(rustc_attrs)]
|
#![feature(rustc_attrs)]
|
||||||
#![feature(allocator_api)]
|
#![feature(allocator_api, unsafe_unpin)]
|
||||||
|
|
||||||
use std::marker::PhantomPinned;
|
use std::marker::{PhantomPinned, UnsafeUnpin};
|
||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
use std::num::NonZero;
|
use std::num::NonZero;
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
|
|
@ -259,11 +259,21 @@ pub fn trait_raw(_: *const dyn Drop) {}
|
||||||
|
|
||||||
// CHECK: @trait_box(ptr noalias noundef nonnull align 1{{( %0)?}}, {{.+}} noalias noundef readonly align {{.*}} dereferenceable({{.*}}){{( %1)?}})
|
// CHECK: @trait_box(ptr noalias noundef nonnull align 1{{( %0)?}}, {{.+}} noalias noundef readonly align {{.*}} dereferenceable({{.*}}){{( %1)?}})
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub fn trait_box(_: Box<dyn Drop + Unpin>) {}
|
pub fn trait_box(_: Box<dyn Drop + Unpin + UnsafeUnpin>) {}
|
||||||
|
|
||||||
|
// Ensure that removing *either* `Unpin` or `UnsafeUnpin` is enough to lose the attribute.
|
||||||
|
// CHECK: @trait_box_pin1(ptr noundef nonnull align 1{{( %0)?}}, {{.+}} noalias noundef readonly align {{.*}} dereferenceable({{.*}}){{( %1)?}})
|
||||||
|
#[no_mangle]
|
||||||
|
pub fn trait_box_pin1(_: Box<dyn Drop + Unpin>) {}
|
||||||
|
// CHECK: @trait_box_pin2(ptr noundef nonnull align 1{{( %0)?}}, {{.+}} noalias noundef readonly align {{.*}} dereferenceable({{.*}}){{( %1)?}})
|
||||||
|
#[no_mangle]
|
||||||
|
pub fn trait_box_pin2(_: Box<dyn Drop + UnsafeUnpin>) {}
|
||||||
|
|
||||||
// CHECK: { ptr, ptr } @trait_option(ptr noalias noundef align 1 %x.0, ptr %x.1)
|
// CHECK: { ptr, ptr } @trait_option(ptr noalias noundef align 1 %x.0, ptr %x.1)
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub fn trait_option(x: Option<Box<dyn Drop + Unpin>>) -> Option<Box<dyn Drop + Unpin>> {
|
pub fn trait_option(
|
||||||
|
x: Option<Box<dyn Drop + Unpin + UnsafeUnpin>>,
|
||||||
|
) -> Option<Box<dyn Drop + Unpin + UnsafeUnpin>> {
|
||||||
x
|
x
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue