rfcs icon indicating copy to clipboard operation
rfcs copied to clipboard

[RFC] field projections v2

Open BennoLossin opened this issue 1 year ago • 18 comments

Rendered

This is my second attempt at a field projection RFC, here is the first.


Add field projections, a very general operation. In simple terms, it is a new operator that turns a generic container type C<T> containing a struct T into a container C<F> where F is a field of the struct T. For example given the struct:

struct Foo {
    bar: i32,
}

One can project from &mut MaybeUninit<Foo> to &mut MaybeUninit<i32> by using the new field projection operator:

impl Foo {
    fn initialize(this: &mut MaybeUninit<Self>) {
        let bar: &mut MaybeUninit<i32> = this->bar;
        bar.write(42);
    }
}

Special cases of field projections are pin projections, or projecting raw pointers to fields *mut Foo to *mut i32 with improved syntax over &raw mut (*ptr).bar.

BennoLossin avatar Dec 04 '24 10:12 BennoLossin

What is the implementation of Projectable for Cow<'_, T>?

jacob-greenfield avatar Dec 04 '24 18:12 jacob-greenfield

@Coder-256

What is the implementation of Projectable for Cow<'_, T>?

added the implementation to the RFC

BennoLossin avatar Dec 04 '24 19:12 BennoLossin

I see, I'm still a bit confused. The value in Cow::Owned has type <T as ToOwned>::Owned, which is not always the same as T. For example, this wouldn't work for Cow<str> I think. Maybe you just need a T: Clone bound though?

Edit: Cow<str> is a bad example obviously (it has no fields), but you can also implement ToOwned on a non-Clone struct and choose a custom type for Owned.

jacob-greenfield avatar Dec 04 '24 19:12 jacob-greenfield

I see, I'm still a bit confused. The value in Cow::Owned has type <T as ToOwned>::Owned, which is not always the same as T. For example, this wouldn't work for Cow<str> I think. Maybe you just need a T: Clone bound though?

Edit: Cow<str> is a bad example obviously (it has no fields), but you can also implement ToOwned on a non-Clone struct and choose a custom type for Owned.

Oh, I totally forgot about ToOwned... I fixed it for now by only providing it if the owned type is the same as the borrowed type.

BennoLossin avatar Dec 04 '24 19:12 BennoLossin

Could you please add a section discussing how we can handle types where the projection might have a different type than the original?

For instance, due to the in-memory layout, we can't turn an Arc<StructType> into an Arc<FieldType>, but we could have some kind of ArcRef type that references an external reference count, and projection could work for that type. So at a minimum we should document how we can handle things like that. But I'm also wondering if we can have some kind of associated type that would allow type-changing field projections:

let x: Arc<StructType> = Arc::new(StructType { field: "a string".to_string() });
let y: ArcRef<String> = x~field;

That would be nicer than having to do something like x.to_arc_ref()~field.

joshtriplett avatar Dec 04 '24 19:12 joshtriplett

@joshtriplett I added ArcRef in future possibilities. The current proposal already supports changing the type when projecting, so there is no need to do x.to_arc_ref()->field (I'm continuing to use -> until we change it in the RFC :)

BennoLossin avatar Dec 04 '24 20:12 BennoLossin

I think this would be very useful for domain-specific languages embedded in Rust, e.g. in fayalite, I've implemented field access for Expr<T> (a struct representing an expression of type T) by having Expr<T> impl Deref into a struct with a field of type Expr<F> for each field of T where the field's type is F. This works but is somewhat inefficient.

programmerjake avatar Dec 04 '24 21:12 programmerjake

I think this would be very useful for domain-specific languages embedded in Rust, e.g. in fayalite, I've implemented field access for Expr<T> (a struct representing an expression of type T) by having Expr<T> impl Deref into a struct with a field of type Expr<F> for each field of T where the field's type is F. This works but is somewhat inefficient.

Yes, I already thought that something like this would be possible, but I didn't have any applications. Any suggestions for how I could mention this in the motivation?

BennoLossin avatar Dec 04 '24 22:12 BennoLossin

@joshtriplett I added ArcRef in future possibilities. The current proposal already supports changing the type when projecting,

Thanks! I'd missed the nuance that it was already possible to change types when projecting.

Then, syntax aside, :+1: to this proposal. I look forward to using it. :)

joshtriplett avatar Dec 04 '24 23:12 joshtriplett

I didn't expect a solution for Cell projections, Pin projections, and improved raw ptr ergonomics in a single RFC. This is a nice early Christmas gift. :)

RalfJung avatar Dec 06 '24 19:12 RalfJung

Would it make sense to implement Project for types like Option and maybe Result with something like

impl<T: Projectsble> Projectable for Option<T> {
    type Inner =  <T as Projectable>::Inner;
}

impl<T, F> Project<F> for Option< T>
where
    F: Field<Base = Self::Inner>,
    T: Project<F>,
{
    type Output = Option<F::Type>;

    fn project(self) -> Self::Output {
        match self {
          Some(v) => Some(v.project())
          None => None
    }
}

Unfortunately, I don't see how to implement it soundly for all T, since doing so would mean moving the field, so you would need an Option of some reference type.

It could also be implemented for all T if F::Type: Copy, but you can't have both, because of coherence, and I think having it for options of reference types is more useful.

tmccombs avatar Dec 07 '24 03:12 tmccombs

@tmccombs this has the same issue as applying Projectable on Cow<T>, which is that Option<T> is a container of T rather than a pointer to T, meaning accessing its field would be necessarily destructive. Supporting these requires "MoveableField" discussed at the end of the RFC.

If Projectable does not need to be a pointer, the distinction between it and #[projecting] would be blurred, and it raises the question again that why not implement Projectable for #[projecting] containers.

kennytm avatar Dec 07 '24 06:12 kennytm

accessing its field would be necessarily destructive.

Not if it is projecting through another pointer type, so for some struct Foo, Moveable field would be necessary for Option<Foo> but not for, say Option<&Foo>.

And even if there is a MoveableField, then using that would mean you can't project an Option<&T> for a non-moveable field.

But perhaps it would be more useful if you could project on an &Option or &mut Option. Unfortunately, implementing Project on those would conflict with the impl for &T and &mut T, but perhaps there is a way that #[projecting] could work on enums? I think that might be what the generalized enum projection section is talking about ?

tmccombs avatar Dec 07 '24 07:12 tmccombs

accessing its field would be necessarily destructive.

Not if it is projecting through another pointer type, so for some struct Foo, Moveable field would be necessary for Option<Foo> but not for, say Option<&Foo>.

Yes, for Option<P> where P is itself a pointer type, this works. I actually think your implementation from above makes sense:

impl<T: Projectsble> Projectable for Option<T> {
    type Inner =  <T as Projectable>::Inner;
}

impl<T, F> Project<F> for Option< T>
where
    F: Field<Base = Self::Inner>,
    T: Project<F>,
{
    type Output = Option<F::Type>;

    fn project(self) -> Self::Output {
        match self {
          Some(v) => Some(v.project())
          None => None
    }
}

Project is implemented for pointer-like types, so adding this impl would allow projecting all sorts of things (for example Option<&mut MaybeUninit<T>> or even multiple nested stuff Option<&mut UnsafeCell<MaybeUninit<T>>).

And even if there is a MoveableField, then using that would mean you can't project an Option<&T> for a non-moveable field.

But perhaps it would be more useful if you could project on an &Option or &mut Option. Unfortunately, implementing Project on those would conflict with the impl for &T and &mut T, but perhaps there is a way that #[projecting] could work on enums? I think that might be what the generalized enum projection section is talking about ?

I think that projecting through &Option<T> is not possible, since the memory layout of Option<T> is not the same as T, thus a value of type Option<T> does not "contain" a field of type Option<F> if F is a field of T.

The section on enum projection is about enabling to use enums instead of structs: with the current proposal, one can project Pin<&mut T> to Pin<&mut F> if F is a structurally pinned field of T, but if T is instead an enum with a single variant and a single structurally pinned field, the same is not possible. The section on enum projections is about allowing that. Not about enabling projections for container types that enums, since for them, adding #[projecting] is impossible for the above reason and Project can be implemented for any type, so they are already supported.

BennoLossin avatar Dec 07 '24 10:12 BennoLossin

May I ask that we move conversations about specific points to a review thread on some part of the diff? I believe the project recently decided this was the best practice, though I can't seem to find a link for that. Either way, this makes it easier to follow multiple conversations and mark it as resolved.

Nadrieril avatar Dec 07 '24 11:12 Nadrieril

@Nadrieril #3717, not yet merged, mainly because inline discussion has crap UX. This is off-topic, let continue over there if you like to. (Also if someone started a discussion in the main thread, there is no way to "move" it to a specific file or diff. To reply you have no choice but to keep using the main thread, like this comment :crying_cat_face:)

kennytm avatar Dec 07 '24 11:12 kennytm

a new standard library use-case just came up: converting Simd<*const MyStruct, N> to Simd<*const MyStructsField, N>, also for enums.

programmerjake avatar Apr 21 '25 19:04 programmerjake

For anyone interested & not already aware, there is a design meeting scheduled for next Wednesday (the 13th of August): https://github.com/rust-lang/lang-team/issues/335.

BennoLossin avatar Aug 09 '25 16:08 BennoLossin