Skip to content

ref pin mut pattern unsoundly allows pin-projecting arbitrary types #157634

@theemathas

Description

@theemathas

@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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-patternsRelating to patterns and pattern matchingA-pinArea: PinC-bugCategory: This is a bug.F-pin_ergonomics`#![feature(pin_ergonomics)]`I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions