If now been trying for a while to make it easy and possible to have a pin on stack/in future which uninitialized, making it initialized in place and then having it behave like a normal pin afterwards. And I'm not the only one, this is wanted and tried by crates like pin_init
, pinned_init
and owned_pin
. However, when I thought about that, one specific thing won't get out of my mind:
Essentially, in the most simple form, I want an API like this:
let a = uninit_pin!(); // a: Pin<&mut MaybeUninit<i32>>
let a = a.init(42); // a: Pin<&mut i32>
But this is way more complicated than you might think at first glance.
The pin having different types while being on the same memory position isn't a problem, because it would have the same memory layout, but what is is that the Drop glue isn't actually on the Pin type, it is inside std's pin!() macro.
For me, this is a super weird phenomenon, because as far as I know, this would make pin! achieve something which is impossible for any other (at least declarative) macro.
Because what pin! essentially does is write outside it's boundaries.
For the case where we aren't in an async context, writing
let a = pin!(42);
essentially composes down to:
let a = 42;
let a = Pin::new_unchecked(&mut a);
Where it writes outside it's callplace and we then also can't name a. And this is HUGE.
While I am here trying to make a pin!(ManuallyDrop::new(...))
and handling the drop in a wrapper in the pin pointer, essentially having a drop of Pin<Handle<i32>>
dropping the pin, so that I can freely transmute that handle to whatever drop glue I want to run on that pinned memory, which is unsound because I can just mem::forget
that handle and have successfully violated pin guidelines, which is why I am crying for the Forget
RFC to get merged, std's pin!
macro is just like "nah, we just hide it from the user and nobody can forget it".
So here's a thought of mine:
Couldn't we combine the magic of pin!
with the ability to initialize in place in a new std item (or someone tells me that this is somehow doable in proc macros ._.):
let a: Pin<&mut i32> = morph_pin!(MaybeUninit::uninit(), initializer);
fn initializer<'brand>(pin: MorphPin<'brand, MaybeUninit<i32>>) -> MorphPin<'brand, i32> {
pin.write(42)
}
which would then lower to this:
let a = ManuallyDrop::new(MaybeUninit::uninit());
let a = MorphPin::new(&mut a); // this handles the drop code - currently as MaybeUninit<i32>
let a = initializer(a); // now drop is handled as i32
let a = a.as_pin(); // we name-shadow a so that the drop handler can't be forgotten anymore
Which should be completely sound.
And this should also be possible in async context because this should also work:
async fn test() {
{
let a = pin!(42);
one_time().await;
let _ = a;
} // a lived over 1 await, but is dropped here.
one_time().await;
}
What do you think about this? (god I'm so terrible at clearly writing out what I want :c)