(oh fun I'm about to suggest using ref
patterns)
For this post I'm using #[flatten]
, but it could also be #[repr(flatten)]
, #[repr(inline)]
, or whatever else.
Let's consider the following:
struct Option8<T>(
#[flatten] pub Option<T>,
#[flatten] pub Option<T>,
#[flatten] pub Option<T>,
#[flatten] pub Option<T>,
#[flatten] pub Option<T>,
#[flatten] pub Option<T>,
#[flatten] pub Option<T>,
#[flatten] pub Option<T>,
);
We want to be able to optimize it into:
struct Option8<T> {
init_flags: u8,
0: MaybeUninit<T>,
1: MaybeUninit<T>,
2: MaybeUninit<T>,
3: MaybeUninit<T>,
4: MaybeUninit<T>,
5: MaybeUninit<T>,
6: MaybeUninit<T>,
7: MaybeUninit<T>,
}
And ideally we'd be able to use it equivalently to the expanded version, but safely. I believe we can specify this succinctly enough:
Guide Level
A field of a #[repr(Rust)] struct
may be marked as #[flatten]
. If a field is marked as #[flatten]
, you are not allowed to take a reference to it, nor to take its address with ptr::addr_of!
. The only legal operations, enforced by the compiler, are to 1) move into the field, potentially dropping the value that was already there; 2) move out of the field, potentially performing a copy, otherwise performing a destructive move; or finally 3) pattern match the field, so long as the pattern match does not require taking a reference to the field.
To illustrate:
let option8: Option8<_> = /* ... */;
match &option8.0 {} // already illegal, takes reference to flattened field
match &option8 {
Option8(a, ..) => {} // illegal, takes reference to flattened field (via default by-ref binding mode)
Option8(Some(a), ..) => {} // legal, takes reference to contained value
Option8(None, ..) => {} // legal, no references taken
Option8(a @ Some(b), ..) => {} // illegal, takes reference to flattened field
}
match option8 {
Option8(a, ..) => {} // legal, moves out the field
Option8(ref a, ..) => {} // illegal, takes reference to flattened field
Option8(Some(a), ..) => {} // legal, moves out the field
Option8(Some(ref a), ..) => {} // legal, takes reference to contained value
}
match option8.0 {
Some(a) => {} // legal, moves out contained value
Some(ref a) => {} // legal, takes reference to contained value
a => {} // legal, moves out the field
ref a => {} // illegal, takes reference to flattened field
}
These restrictions allow the compiler to more aggressively niche values together to produce smaller structure layouts. For example, the compiler would be allowed to (but is not required to) store the eight Option
discriminants in Option8
in a singular byte, as there is no requirement to be able to manifest a reference to a full Option<T>
.
Implementation Concerns
There are two main concerns for implementation: 1) built-in derive support, and 2) drop glue generation. The former is simple enough: do the same as is done for #[repr(packed)]
: make a temporary stack copy and call methods on that. As with #[repr(packed)]
, more complicated cases will just provide custom trait implementations. The problematic concern is the latter.
For types with a Drop
implementation, we need to manifest &mut T
in order to call Drop::drop(&mut self)
. This is identical (again) to #[repr(packed)]
, and again we take the same solution: move the value out onto the stack. This is somewhat unfortunate, as the we would prefer avoiding the copy of possible, but unavoidable.
For types with drop glue but no Drop
implementation, a smarter implementation is possible. The drop glue could be enhanced to comply with the restrictions added by #[flatten]
(in all cases, if it doesn't already), and thus avoid the extra copy required to manifest the &mut
.
I particularly enjoy how this avoids any interaction with whether a type is Copy
or not, beyond type checking of emitted #[derive]
d trait implementations. Going into this I was expecting to suggest limiting #[flatten]
to Copy
types, but I think this specified behavior is clean enough to support all types equivalently.
I do not know if the existing pattern matching machinery is set up in such a way that it could support matching types with flattened fields as I've described here. If not, then it could make sense to implement flattening only within Copy
types initially.