#rust #vortex In [Vortex](https://github.com/vortex-data/vortex), we have a type hierarchy that looks something like this: - `DType` - `Struct(Vec<DType>)` - `Primitive(PType)` - where `PType` is broken down into - Signed Int - `I8`, `I16`, `I32`, `I64` - Unsigned Int - `U8`, `U16`, `U32`, `U64` - Float - `F16`, `F32`, `F64` - `Utf8` - ... You get the picture. Pretty often with the primitive types, we'll get a `PrimitiveArray` and we don't know exactly what's inside of it. To make it easier to work with a block of data generically, we have a set of higher-order declarative macros which let us expand and take action generically, substituting the type parameter in where necessary: ```rust let range: usize = match_each_integer_ptype!(array.ptype(), |P| { // Use type parameter P to read the array's value and cast it // to a buffer of `P`'s let xs = array.buffer::<P>(); let max: P = xs.iter().copied().min().unwrap_or_default(); let min: P = xs.iter().copied().max().unwrap_or_default(); (max - min) as usize }) ``` Note that the macro actually accepts a closure-like syntax where the first argument is not a value, but a type-parameter that is bound in the inner function. Here's how we write the macro: ```rust macro_rules! match_each_integer_ptype { ($self:expr, | $tname:ident | $body:block) => {{ use $crate::PType; match $self { PType::I8 => { type $tname = i8; $body } PType::I16 => { type $tname = i16; $body } PType::I32 => { type $tname = i32; $body } PType::I64 => { type $tname = i64; $body } PType::U8 => { type $tname = u8; $body } PType::U16 => { type $tname = u16; $body } PType::U32 => { type $tname = u32; $body } PType::U64 => { type $tname = u64; $body } other => panic!("Unsupported ptype {other}", other = other), } }}; } ``` What's going on here? Let's make like an LLM and think step-by-step: ```rust ($self:expr, | $tname:ident | $body:block) ``` This block sets up the binder for the closure-like syntax from the example above. The first parameter to the macro is some `PType` value, which is the reified runtime enum that describes all of Vortex's primitive types. The next token (after whitespace) must be the pipe `|` character, followed by some identifier we'll bind to `$tname`, followed by another `|`. The next item must be a block, i.e. a valid code block bound by a pair of curly braces `{}`. ```rust match $self { ``` Sets up the match, so we can switch our logic and bind to each possible `PType` variant. ```rust PType::I8 => { type $tname = i8; $body } ``` This is the juicy part. Ok so what's happening? Inside of each match arm, we create a _new scope_. Inside of the scope, we bind a new type alias `$tname = i8`. This is because inside of the `I8` match arm, we want to treat whatever is behind our variable as an `i8`. We then insert the block. The block should be referencing the type parameter that we called `$tname` inside of the closure-style `|` pipes. So if we put it all together, we can see that our original example gets expanded to this larger block: ```rust use ::vortex_dtype::PType; match (array.ptype()) { PType::I8 => { type P = i8; { let xs = array.buffer::<P>(); let max: P = xs.iter().copied().min().unwrap_or_default(); let min: P = xs.iter().copied().max().unwrap_or_default(); (max - min) as usize } } PType::I16 => { type P = i16; { let xs = array.buffer::<P>(); let max: P = xs.iter().copied().min().unwrap_or_default(); let min: P = xs.iter().copied().max().unwrap_or_default(); (max - min) as usize } } PType::I32 => { type P = i32; { let xs = array.buffer::<P>(); let max: P = xs.iter().copied().min().unwrap_or_default(); let min: P = xs.iter().copied().max().unwrap_or_default(); (max - min) as usize } } PType::I64 => { type P = i64; { let xs = array.buffer::<P>(); let max: P = xs.iter().copied().min().unwrap_or_default(); let min: P = xs.iter().copied().max().unwrap_or_default(); (max - min) as usize } } PType::U8 => { type P = u8; { let xs = array.buffer::<P>(); let max: P = xs.iter().copied().min().unwrap_or_default(); let min: P = xs.iter().copied().max().unwrap_or_default(); (max - min) as usize } } PType::U16 => { type P = u16; { let xs = array.buffer::<P>(); let max: P = xs.iter().copied().min().unwrap_or_default(); let min: P = xs.iter().copied().max().unwrap_or_default(); (max - min) as usize } } PType::U32 => { type P = u32; { let xs = array.buffer::<P>(); let max: P = xs.iter().copied().min().unwrap_or_default(); let min: P = xs.iter().copied().max().unwrap_or_default(); (max - min) as usize } } PType::U64 => { type P = u64; { let xs = array.buffer::<P>(); let max: P = xs.iter().copied().min().unwrap_or_default(); let min: P = xs.iter().copied().max().unwrap_or_default(); (max - min) as usize } } other => panic!("Unsupported ptype {other}", other = other), } } ```