@Dnreikronos discovered in #157615 that, while the pin_ergonomics feature allows implicit pin projections (via binding mode stuff) only on #[pin_v2] types, it allows explicit pin projections with ref pin mut patterns on any type. I think this is incorrect.
This pin projection can be done even on types that shouldn't be structurally pinned. This is unsound, as the code below demonstrates by breaking the Pin guarantee.
#![feature(pin_ergonomics)]
#![expect(incomplete_features)]
use std::marker::PhantomPinned;
use std::pin::{Pin, pin};
struct Thing<T>(T);
impl<T> Unpin for Thing<T> {}
// Pins the value, calls the callback with it, then returns back the value.
// This function violates the Pin guaragntee.
fn wrong_pin<T>(value: T, callback: impl FnOnce(Pin<&mut T>)) -> T {
let mut pinned_thing: Pin<&mut Thing<Option<T>>> = pin!(Thing(Some(value)));
// pinned_option has type Pin<&mut Option<T>>
let &pin mut Thing(ref pin mut pinned_option) = pinned_thing;
let pinned_value: Pin<&mut T> = pinned_option.as_pin_mut().unwrap();
callback(pinned_value);
// Allowed because Thing implements Unpin
let mut_thing: &mut Thing<Option<T>> = &mut *pinned_thing;
mut_thing.0.take().unwrap()
}
struct NotUnpin(#[expect(dead_code)] i32, PhantomPinned);
fn main() {
let value = NotUnpin(1_i32, PhantomPinned);
let returned_value: NotUnpin = wrong_pin(value, |pinned_value: Pin<&mut NotUnpin>| {
println!("Pinned at {pinned_value:p}");
});
println!("Moved to {:p}", &returned_value);
}
Output:
Pinned at 0x7fff2721d958
Moved to 0x7fff2721d9cc
The Thing type unconditionally implements Unpin. This implies that the type should not be structurally pinned. The code then uses this to pin a non-Unpin type at one memory address, then later move that type to a different memory address, which breaks the Pin guarantee.
Note that async-await currently does not work with pin_ergonomics, due to #153733. If it did, it would be possible to use this guarantee-breaking to directly cause UB in safe code with async-await.
Meta
Reproducible on the playground with version 1.98.0-nightly (2026-06-07 f20a92ec01483dc5c58e)
@Dnreikronos discovered in #157615 that, while the
pin_ergonomicsfeature allows implicit pin projections (via binding mode stuff) only on#[pin_v2]types, it allows explicit pin projections withref pin mutpatterns on any type. I think this is incorrect.This pin projection can be done even on types that shouldn't be structurally pinned. This is unsound, as the code below demonstrates by breaking the
Pinguarantee.Output:
The
Thingtype unconditionally implementsUnpin. This implies that the type should not be structurally pinned. The code then uses this to pin a non-Unpintype at one memory address, then later move that type to a different memory address, which breaks thePinguarantee.Note that async-await currently does not work with
pin_ergonomics, due to #153733. If it did, it would be possible to use this guarantee-breaking to directly cause UB in safe code with async-await.Meta
Reproducible on the playground with version
1.98.0-nightly (2026-06-07 f20a92ec01483dc5c58e)