Skip to main content

array_slots

Attribute Macro array_slots 

Source
#[array_slots]
Expand description

Generate slot index constants, a borrowed view struct, and a typed ext trait from a slot struct definition.

Fields must be ArrayRef (required slot), Option<ArrayRef> (optional slot), or Vec<ArrayRef> (variadic tail of required slots).

Every field must carry a #[slot(..)] attribute naming the exact slot index it maps to. The attribute — not the declaration order — defines the storage layout, so fields may be reordered, grouped, or documented in any order without changing the slot indices an array is built from or read back with.

§Example

#[array_slots(Patched)]
pub struct PatchedSlots {
    #[slot(0)]
    pub inner: ArrayRef,
    #[slot(1)]
    pub lane_offsets: ArrayRef,
    #[slot(2)]
    pub patch_indices: ArrayRef,
    #[slot(3)]
    pub patch_values: ArrayRef,
}

§Generated output

Given the above, the macro generates:

// --- The original struct, minus the consumed `#[slot(..)]` attributes ---
pub struct PatchedSlots { ... }

// --- Slot index constants and conversion methods on the struct ---
impl PatchedSlots {
    pub const INNER: usize = 0;
    pub const LANE_OFFSETS: usize = 1;
    pub const PATCH_INDICES: usize = 2;
    pub const PATCH_VALUES: usize = 3;
    pub const COUNT: usize = 4;
    pub const NAMES: [&'static str; 4] = ["inner", "lane_offsets", "patch_indices", "patch_values"];

    /// Take ownership of slots from an `ArraySlots`.
    pub fn from_slots(slots: ArraySlots) -> Self { ... }

    /// Convert back into storage order.
    pub fn into_slots(self) -> ArraySlots { ... }
}

// --- Borrowed view with &ArrayRef / Option<&ArrayRef> fields ---
pub struct PatchedSlotsView<'a> {
    pub inner: &'a ArrayRef,
    pub lane_offsets: &'a ArrayRef,
    pub patch_indices: &'a ArrayRef,
    pub patch_values: &'a ArrayRef,
}

impl<'a> PatchedSlotsView<'a> {
    pub fn from_slots(slots: &'a [Option<ArrayRef>]) -> Self { ... }
    pub fn to_owned(&self) -> PatchedSlots { ... }
}

// --- Ext trait with per-field accessors + slots_view() ---
pub trait PatchedArraySlotsExt: TypedArrayRef<Patched> {
    fn inner(&self) -> &ArrayRef { ... }         // indexes slots directly
    fn lane_offsets(&self) -> &ArrayRef { ... }
    fn patch_indices(&self) -> &ArrayRef { ... }
    fn patch_values(&self) -> &ArrayRef { ... }
    fn slots_view(&self) -> PatchedSlotsView<'_> { ... }
}

impl<T: TypedArrayRef<Patched>> PatchedArraySlotsExt for T {}

§Slot index annotations

  • Fixed fields use #[slot(N)], where N is the exact index of the slot.
  • A variadic tail uses #[slot(N..)], where N is the index its first slot occupies.

The annotations are validated at compile time: the fixed indices must cover 0..FIXED_COUNT exactly — no duplicates and no gaps — and a variadic tail must start immediately after the last fixed slot. A field without a #[slot(..)] attribute is a compile error, so the layout can never silently fall back to declaration order.

§Required vs optional slots

  • ArrayRef — the slot must be present. from_slots() panics if None. The ext trait accessor returns &ArrayRef. The view field is &'a ArrayRef.

  • Option<ArrayRef> — the slot may be absent. from_slots() preserves None. The ext trait accessor returns Option<&ArrayRef>. The view field is Option<&'a ArrayRef>.

The underlying storage is always ArraySlots — the field type only controls whether the macro inserts a .vortex_expect() unwrap or not.

§Variadic tail slots

One field may be Vec<ArrayRef>, declaring that every slot from its index onward belongs to a homogeneous, variable-length run of required slots. This supports encodings like Chunked ([chunk_offsets, chunks...]), Struct ([validity?, fields...]), and Union ([type_ids, children...]).

#[array_slots(Chunked)]
pub struct ChunkedSlots {
    #[slot(0)]
    pub chunk_offsets: ArrayRef,
    #[slot(1..)]
    pub chunks: Vec<ArrayRef>,
}

For a struct with a variadic tail, the macro generates a different set of constants — slot count is no longer a compile-time constant:

impl ChunkedSlots {
    pub const CHUNK_OFFSETS: usize = 0;
    /// Offset at which the `chunks` slots begin.
    pub const CHUNKS_OFFSET: usize = 1;
    /// Number of fixed (non-variadic) slots.
    pub const FIXED_COUNT: usize = 1;
    /// Names of the fixed slots in storage order.
    pub const FIXED_NAMES: [&'static str; 1] = ["chunk_offsets"];

    /// Name of the slot at `idx`, e.g. "chunk_offsets" or "chunks[3]".
    pub fn slot_name(idx: usize) -> String { ... }

    pub fn from_slots(slots: ArraySlots) -> Self { ... }
    pub fn into_slots(self) -> ArraySlots { ... }
}

The view field and ext trait accessor for the tail are a vortex_array::SlotSlice, a borrowed run of required slots supporting len(), get(), iter(), and indexing:

pub struct ChunkedSlotsView<'a> {
    pub chunk_offsets: &'a ArrayRef,
    pub chunks: SlotSlice<'a>,
}

pub trait ChunkedArraySlotsExt: TypedArrayRef<Chunked> {
    fn chunk_offsets(&self) -> &ArrayRef { ... }
    fn chunks(&self) -> SlotSlice<'_> { ... }
    fn slots_view(&self) -> ChunkedSlotsView<'_> { ... }
}