Cast variable size data from SPI to array of structs

I am trying to write an embedded-hal-async SPI driver.(Pixy2 camera)

There should be a function approximately:

pub async get_blocks(&mut self) -> Result<&[Block]>

The amount of blocks(length of data) is known only when the packet header is read.

Block is a struct.

pub struct Block {
    pub signature: u16,
    pub x: u16,
    pub y: u16,
    pub width: u16,
    pub height: u16,
    pub angle: i16,
    pub index: u8,
    pub age: u8,
}

How do I efficienctly(no alloc) and idiomatically implement the reading of the data from SPI and getting this array from the data?

Btw I am referencing this C++ code:

I tried something very broken with bytemuck, zerocopy and hacky and it does not work, better not including that here.

Should I put a buffer into the Pixy2 driver sturct field?

How such things are usually solved?

if you want a no alloc solution, basically there's only two way to provide the backing memory: caller provided and static storage.

btw, it seems the C++ code does allocate the buffer internally? or am I looking at the wrong place?

In addition to nerditation's answer, if it is hard for you to provide a buffer that will go between the driver and the user (though imo cycling two static buffers is the best solution), you can just accept a closure that you'll call when a new block arrives. Then the user can decide what to do with the block themselves, like push into a heapless::Vec or something.

Thank you for the responses!
I decided to go with a buffer in the Pixy2 struct field:

const BUF_SIZE: usize = 0x104;
pub struct Pixy2<SPI> {
    spi: SPI,
    buf: [u8; BUF_SIZE],
}

And then using zerocopy like:

return Ok(<[Block]>::ref_from_bytes(&self.buf[0..len]).unwrap());
// Returns Result<&[Block]>;

Turns out

[repr(C, packed)]

is very important for Block struct, otherwise it crashes on casting with alignment errors.
(However this makes it is hard to use defmt for this)

It works but maybe not idiomatic. Improvements suggestions are welcome.

My code:

@nerditation I am not sure, but the c++ code seems to allocate buffer only on instantiation of the driver type.

I do not want the caller to deal with buffers, I wanted easy to use API.
Btw here is usage example if you are interested

You need buf's start to be aligned to whatever Block's required alignment is. (It is 2 bytes.)

You could do it by putting a [Block; 0] before it and marking the struct with #[repr(C)] simply; it will probably help with defmt.

Any chance that your data comes in pairs of bytes at least, so that you could use u16?