[Pre-RFC] `field_projection`

You have a field attribute with no attribute on the struct (unlike any existing proc macros, including pin-project). The name of that attribute is declared somewhere else entirely, even in another crate. How do you prevent or deal with name collisions? Does the attribute have to be imported?

Even if you just consider #[pin] as a special thing just for Pin (unrelated to defining the name in field_projecting), where else in the Rust language as implemented by the compiler (without external crates) is there precedent for a standalone field attribute like this?

If you expose a field that should be pinned as unpinned, then you allow moving out of that field, and potentially invalidating any self-references elsewhere in the struct.

Example:

#![feature(maybe_uninit_uninit_array)]

use std::pin::Pin;
use std::marker::PhantomPinned;
use std::mem::MaybeUninit;

struct NotUnpin {
    inner: u8,
    _pin: PhantomPinned,
}

struct Unmovable {
    data: [MaybeUninit<NotUnpin>; 16],
    len: usize,
    // pointer into `data` or null
    cursor: *mut NotUnpin,
    _pin: PhantomPinned,
}

impl Unmovable {
    fn new() -> Pin<Box<Self>> {
        Box::pin(Unmovable {
            data: MaybeUninit::uninit_array(),
            len: 0,
            cursor: std::ptr::null_mut(),
            _pin: PhantomPinned,
        })
    }
    
    fn push(self: Pin<&mut Self>, item: u8) {
        assert!(self.len < 16);
        let item = NotUnpin {
            inner: item,
            _pin: PhantomPinned
        };
        
        // Safety: we do not move anything out of the mutable reference
        unsafe {
            let this = self.get_unchecked_mut();
        
            this.data[this.len].write(item);
            this.cursor = this.data[this.len].as_mut_ptr();
            this.len += 1;
        }
    }
    
    fn get_item(self: Pin<&Self>) -> Option<&u8> {
        if self.cursor.is_null() {
            None
        } else {
            Some(unsafe { &(*self.cursor).inner })
        }
    }
}

Maybe I'm doing something else wrong here, or have a misunderstanding or how the projection should desugar. But it seems like if you were to forget to mark data as #[pin], you could easily invalidate cursor with safe code:

fn main() {
    let mut u = Unmovable::new();
    
    dbg!(u.as_ref().get_item());
    u.as_mut().push(1);
    u.as_mut().push(2);
    dbg!(u.as_ref().get_item());
    
    {
        let mut data: Pin<&mut [MaybeUninit<_>; 16]> =  
        // this is just the desugaring of `&mut u.as_mut().data`
        // if `data` is marked with `#[pin]`
        unsafe { u.as_mut().map_unchecked_mut(|u| &mut u.data) };
        
        // error[E0594]: cannot assign to data in dereference 
        // of `Pin<&mut [MaybeUninit<NotUnpin>; 16]>`
        // *data = MaybeUninit::uninit_array();
    }
    
    let data: &mut [MaybeUninit<_>; 16] =  
        // this is just the desugaring of `&mut u.as_mut().data`
        // if `data` is NOT marked with `#[pin]`
        unsafe { &mut u.as_mut().get_unchecked_mut().data };
    
    *data = MaybeUninit::uninit_array();
    
    // woops now `cursor` points to uninitialized memory
    dbg!(u.as_ref().get_item()); // creates a reference to uninitalized memory
}

Yeah, on second thought, there would be no way to exclude certain variants from projection, if they contain $T, so the target attribute is unnecessary.

Well, if it's not useful to exclude certain fields from projection (I don't know if it is or not), then you'd only need to list the unwrap fields.

I think having some attribute at an entirely different location in the code, possibly in the standard library or another crate, define a special word just for this purpose will just lead to confusion. I'm sure it also makes implementing this in the compiler far more difficult. It's better to just have a standard general word for it.

I don't like the second approach either, and I certainly don't think it's better than using an attribute on the struct instead.

I still don't understand why you can't just treat

struct RaceFutures<F1, F2> {
    fut1: F1,
    fut2: F2,
    #[unpin]
    first: bool,
}

like

struct RaceFutures<F1, F2> {
    #[pin]
    fut1: F1,
    #[pin]
    fut2: F2,
    first: bool,
}

To detect if there's a pinned field, you just check if a given field is not marked with #[unpin]. Checking for the absence of #[unpin] is the same as checking for #[pin] and vice-versa.