Idea: DerefPin/DerefCell


#1

Currently, Pin offers the unsafe map method, which is safe to use when returning a Pin to a struct field or other interior pointer that preserves Pin's constraints. There is the idea of a #[derive]-like macro to automatically generate safe accessor methods to make this nicer.

Similarly, Cell has RFC 1789, which offers a safe conversion from &Cell<[T]> to &[Cell<T>]. This should also apply to struct fields, but there’s no easy way to express this in the type system.

If Pin<'a, T> and &'a Cell<T> were part of the language, these operations could be built in. But that’s a lot of surface area to add to the language. What if we had something like Deref that applied to types like this, where deref returned a *T that the compiler would re-wrap in a Pin/&Cell after field projection or indexing? This could potentially be a much smaller language change to get the same ergonomic wins.

Could such a trait be shared between Pin and &Cell? At first glance it seems the two types of references can be projected into using the same rules. If not, it would need to be two traits, which is not much better than baking Pin and &Cell into the language.

Are there any other reference types that could take advantage of this? &RefCell might be able to use a wide pointer that keeps a reference back to its counter, but I’m not sure how useful that would be. Or is it too specific? I can imagine "map a field projection" working with Option or Result as well, where it wouldn’t be constrained by the same rules as Pin/&Cell.

Thoughts?


#2

This sounds interesting! So essentially this would an an unsafe-to-implement trait that reference types could implement to declare that Ref<'a, T> where T is a struct with a public field f of type U, can be turned into Ref<'a, U> pointing to the field? Really this is a trait that wants to be implemented for type constructors (things of kind Lifetime -> * -> *), not types. I think this needs associated type constructors, and even then it remains somewhat awkward. Something like

trait AccessFieldField {
  type<'a, T> Ref;

  unsafe fn access_field<'a, T, U, F>(
    x: Self,
    map: F
  ) -> Ref<'a, U>
where
  Self = Ref<'a, T>,
  F: FnOnce(*const T) -> *const U;
}

(I know equality constraints are not a thing. Anyways.)

Then x.field would compile to unsafe { x.access_field(|x| &(*x).field as *const _) }.

cell::Ref and cell::RefMut come to my mind.


#3

Does the fact that one of them is a “pointee type” (Cell, which needs an & in addition to make it a reference) and the other is a “pointer type” (Pin, which is already a reference) not throw a wrench into the works?

(In an ideal world, maybe & and &mut themselves would’ve worked this way, instead of autoderef, forming a coherent story together with what we now call “default binding modes”.)


#4

Could this be done via some auto-deriving mechanism instead?

I also wonder if it could / should apply to non-reference stuff but arbitrary_self_types cases such as:


#5

@RalfJung Hm, you’re right- it does kind of call out for HKT. I wonder if, since it’s already compiler magic, whether it would be worth just special casing it? Or if that would be too icky? Maybe split the reconstruction into a separate trait and have the compiler match them up on its own:

trait DerefField { // impl<'a, T> for Pin<'a, T>
    type T;
    unsafe fn deref_field(self) -> *const T;
}

trait RefField { // impl<'a, U> for Pin<'a, U>
    type U;
    unsafe fn ref_field(*const U) -> Self;
}

Despite the ickiness this should work for both Pin<'a, T> and &'a Cell<T>, right?

:smiley: I’ve liked this idea since the current incarnation of default binding modes was worked out.

That would presumably wind up turning field access into method access, which is why I brought this version up to begin with.


#6

I think the relevant type would be &Cell<T>, which is a pointer. The fact that it is composed of two types should not be a problem, hopefully.

Well but how exactly would the matching-up works?

Also, I was just reminded of the family pattern, and that could maybe even express the right trait without using equality constraints. However, I still have no idea how inference would work:

trait RefFamily {
  type<'a, T> Ref : Deref<Target=T>;
}

struct ShrRef;
impl RefFamily for ShrRef {
  type<'a, T> Ref = &'a T;
}

struct MutRef;
impl RefFamily for MutRef {
  type<'a, T> Ref = &'a mut T;
}

struct CellRef;
impl RefFamily for CellRef {
  type<'a, T> Ref = &'a Cell<T>;
}

struct PinRef;
impl RefFamily for PinRef {
  type<'a, T> Ref = Pin<'a, T>;
}

trait DerefField : RefFamily {
  unsafe fn access_field<'a, T, U, F>(
    x: Self::Ref::<'a, T>,
    map: F
  ) -> Self::Ref<'a, U>
  where
    F: FnOnce(*const T) -> *const U;
}

One would probably have to call this as CellRef::access_field, i.e. there would still have to be magic for determining the family when desugaring an x.f.


#7

Some good points on the interaction of field projection with Drop, in the case of Pin, here and here.