[RFC] field projections v2
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.
What is the implementation of Projectable for Cow<'_, T>?
@Coder-256
What is the implementation of
ProjectableforCow<'_, T>?
added the implementation to the RFC
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.
I see, I'm still a bit confused. The value in
Cow::Ownedhas type<T as ToOwned>::Owned, which is not always the same asT. For example, this wouldn't work forCow<str>I think. Maybe you just need aT: Clonebound though?Edit:
Cow<str>is a bad example obviously (it has no fields), but you can also implementToOwnedon a non-Clonestruct and choose a custom type forOwned.
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.
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 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 :)
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.
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 typeT) by havingExpr<T>implDerefinto a struct with a field of typeExpr<F>for each field ofTwhere the field's type isF. 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?
@joshtriplett I added
ArcRefin 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. :)
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. :)
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 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.
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 ?
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, sayOption<&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.
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 #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:)
a new standard library use-case just came up: converting Simd<*const MyStruct, N> to Simd<*const MyStructsField, N>, also for enums.
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.