If we’re not committed to having the sweetest possible auto-derefy syntax, we could just (“just”?) add a type for “field F in type T” – like C++ member pointers, basically, hopefully without their baggage (which IINM is mostly around member function pointers) – and then each relevant type can just have an inherent method to apply the projection. We don’t need to give the type special syntax like C++ does, but it would probably require language support for constructing instances of it (unless we can and want to hack it together with macros somehow, which, ick).
To illustrate:
pub struct Field<T, F> { offset: usize }
// (it's an offset, not a size, but fields can't be at negative offsets)
impl<T> Cell<T> {
pub fn field<F>(&self, field: Field<T, F>) -> &Cell<F> { ... }
}
impl<T> Pin<T> {
pub fn field<F>(self, field: Field<T, F>) -> Pin<F> { ... }
}
fn example() {
struct Example { x: i32, y: i32 }
let cell = Cell::new(Example { x: 10, y: 11 });
let cell_y: &Cell<i32> = cell.field(.y);
// (not sure what the precise syntax should be here;
// hopefully the compiler could apply some type-directed name resolution)
}
Notably, only the compiler could safely create instances of Field, so you can rely on it being “valid”.
(This could even allow some “mutable destructuring”, with dynamic checks – e.g. from PinMut you might want to get simultaneous sub-PinMuts to two disjoint fields. To support that, we could have an fn that takes two Fields (and returns two PinMuts) and asserts that the extent of the fields, as calculated from their offsets plus sizes_of, don’t overlap. (Mere inequality checks aren’t enough, especially if we allow “deep” Fields into nested structs: e.g. one could be to an outer struct, and another to its second field, overlapping without having the same offset.))