Для хранения данных используется [bincode](https://crates.io/crates/bincode) и [zstd](https://crates.io/crates/zstd).
Данные вопросов в виде структуры сериализуются через `bincode` в бинарные данные и сжимаются `zstd`. Каждый вопрос сериализуется и сжимается отдельно.
В файле сжатые данные храняться последовательно, после заголовка файла.
### Заголовок файла
В заголовке хранятся только смещения каждого сжатого блока данных, и дополнительно, смещение указывающее на конец файла.
Смещение указывается в виде 32-битного беззнакового целового числа `u32`, сохраненного в виде 4-х байтов от младшего к старшему ([Little Endian](https://ru.wikipedia.org/wiki/Порядок_байтов#Порядок_от_младшего_к_старшему)).
Пример заголовка:
~~~
00: 10 00 00 00
04: 1A 00 00 00
08: 2E 00 00 00
0А: 3A 00 00 00
~~~
данные:
~~~
10: 00 00 00 00 00 00 00 00 00 00
1A: 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00
2E: 00 00 00 00 00 00 00 00 00 00
00 00
3A: (EOF)
~~~
В этом примере сохранены 3 записи:
1. смещение **0x10**, длина **10** байт
2. смещение **0x1A**, длина **20** байт
3. смещение **0x2E**, длина **12** байт
Размер файла - 58 байт (0x3A), размер заголовка 16 байт (0x10).
### Чтение данных
Пусть размер записи заголовка в байтах = **`M`**, количество записей в файле = **`N`**.
Тогда для того чтобы:
- Найти **`N`**, нужно прочитать первую запись (**`M`** байт) в начале файла и разделить её значение на **`M`**;
- Найти элемент данных с индексом **`i`**, нужно последовательно прочитать 2 записи (**`M * 2`** байт), начиная c индекса **`i`** (по смещению в файле: **`i * M`**). Смещением будет значение по индексу **`i`**, длинной - разница между значениями по индексам **`i + 1`** и **`i`**.
Далее для каждого вопроса отдельно предполагается распаковка данных через `zstd` и десериализация через `bincode`.