That is, allow a struct to contain any number of DST fields so long as they are at the end of the definition.
In this case, the lengths of both fields comprise the metadata of the pointer in the form of (usize, usize).
The offset of the second slice may be calculated at runtime using the length of the first and the elements' alignment.
Another desirable feature is slices of DSTs. For this to work, all elements should possess the same metadata.
For example, one could coerce a &[Foo] where Foo: Trait to a &[dyn Trait].
This way, instead of storing many Box<dyn Trait> pointing to the same type, one could allocate a bunch and coerce the whole slice to one of trait objects.
Another usage would be to create nested slices (aka tensors). A matrix, for instance, would be [[T]] where the lengths of the 'rows' are all the same and are contained in the metadata of the pointer - (nrows: usize, row_len: usize).
In general, the metadata of a pointer to a slice of DSTs [T] where T: ?Sized is (usize, <T as Pointee>::Metadata). For regular slices it's (usize, ()) which has the same represantation as usize
My understanding is that this would require significant major changes in the compiler (from what I have heard, I'm not a compiler developer).
However you could probably make a crate with proc-macros to synthesize something like this (using unsafe under the hood and various wrapper structs), if you accept having to use accessor methods rather than direct field access. I could see this being useful for zero copy parsing of network protocols and file formats with multiple dynamically sized fields.
I wonder why impose this restriction. Currently it's needed (because it allows the offset of all fields to be known statically, but if you allow two or more DSTs the it's already not true anymore.
In addition, we should add these stuffs related to ValueSized (as proposed in the pre-RFC):
...Ts: ValueSized implies X<...Ts>: ValueSized, where ...Ts: ValueSized means for each T in Ts, there is T: ValueSized
...Ts: Sized + Unsize<...Us>, ...Us: ValueSized implies X<...Ts>: Unsize<X<...Us>>, where ...Ts: Unsize<...Us> means for each (T, U) in (...Ts, ...Us), there is T: Unsize<U>.
Then, StructOfArrays can be constructed (on stack) in the folllewing steps:
// 1. create the sized value with metadata
let foo: StructOfArrays<
Thin<[f32; 3], [f32]>,
Thin<[f32; 3], [f32]>,
> = StructOfArrays {
xs: Thin::new_unsized([1.0, 2.0, 3.0]),
ys: Thin::new_unsizsd([4.0, 5.0, 6.0]),
};
// 2. create the reference to the sized value
let foo: &StructOfArray<
Thin<[f32; 3], [f32]>,
Thin<[f32; 3], [f32]>,
> = &foo;
// 3. coerce to the unsized reference
// (given that `Thin<[f32; 3], [f32]>: Unsize<Thin<[f32]>>`)
let foo: &StructOfArray = foo;
The offsets of the sized fields should indeed be known statically, even if there are multiple DSTs.
The offset of nything after a DST field must be computed at runtime, so for performance reasons the DST fields should be at the very end
If you’re accepting that calculating the start of the second and later DST fields is going to require runtime computation, why not allow a similar cost for sized fields? I see no reason to limit it if this were supported.
For dynamically sized fields it's impossible to get the offset at compile time. With sized ones - it's trivial. Why pay the cost if you don't have to? What would be the benefit of putting statically sized fields after dynamically sized ones?
For the current limitation of one DST it’s not, it’s got a fixed offset just like all other fields. Supporting multiple DST is mostly about adding this feature of dynamically defined field offsets, once that’s supported I don’t see why it should be limited to when the target field is also a DST.
If you allow (Dst, Dst), then it should also be possible to have (Dst, (u8, Dst)), which has the u8 at a dynamic offset in the outer tuple. It would be surprising to forbid (Dst, u8, Dst) then.