Чего store не примет
Запись отклоняют две разные вещи, и задать вам дано только одну.
Насколько глубоко может вкладываться значение
Заголовок раздела «Насколько глубоко может вкладываться значение»Предел здесь — на форму значения. Структура, которая держит структуру,
которая держит Vec структур, — это уже три уровня, а больше ничего и не
считали. А у типа, который ссылается на себя, — дерево, меню, выражение, —
уровней столько, сколько их окажется у данных в день запуска.
Кодек любого движка читает мельче, чем пишет. serde_json на входе
останавливается на 128, на выходе не ограничен ничем; ron останавливается на
64; у rmp_serde предела нет вовсе — вместо него кончается стек, примерно на
трёх тысячах (мерили на Windows 11 при размере стека по умолчанию; где именно
он кончится, зависит от системы и от того, с каким стеком запущен поток). Это
убивает процесс, а не возвращает ошибку, и повторяется на каждом следующем
запуске, потому что значение уже записано.
Поэтому без проверки запись выше читательского потолка примут, а обратно не прочитают: ошибки нигде нет, а на текстовых движках пропадает весь файл — документ разбирают как одно целое. Отсюда и то, что потолок действует всегда, что бы ещё ни настроили, а отказ приходит на записи.
Считают уровни по ходу записи, а не до неё. Осмотреть значение заранее
нельзя: до store оно доходит как &dyn Serialize, и пятиуровневая структура
там неотличима от пятиуровневого дерева. Построить его, чтобы измерить, тоже
нельзя — как раз построить и опасно: на redb именно это переполняет стек. Serde
проталкивает, store принимает, поэтому уровни и считают по мере прохождения,
внутри собственного прохода кодека.
let engine = default_backend();println!("{}: {} levels", engine.extension(), engine.depth_ceiling());Числа не взяты с потолка, а измерены: tests/probe_*.rs доводит каждый движок
до его границы.
| движок | уровней |
|---|---|
| redb | 512 |
| JSON | 127 |
| SQLite | 127 |
| TOML | 80 |
| RON | 64 |
Своего предела у redb нет. rmp_serde рекурсирует, пока не кончится стек, —
уровне на трёхтысячном на Windows 11 при стеке по умолчанию, — и процесс умирает
вместо того, чтобы вернуть ошибку, и так на каждом следующем запуске. Это число —
замер, а не константа: оно поедет вместе с системой и с тем, какой стек достался
потоку. 512 поставлены ровно поэтому: намного выше любых данных, которые
кто-нибудь собирается хранить, и намного ниже места, где кончается стек на любой
из них.
Путь тратит тот же бюджет
Заголовок раздела «Путь тратит тот же бюджет»На текстовых движках уровни пути становятся уровнями документа: ui.panels.left
— это три уровня вложенности в файле ещё до того, как начнётся значение. Поэтому
их и считают вместе, и мелкое значение по глубокому пути нечитаемо ровно так же,
как глубокое по мелкому.
SQLite тут исключение: путь у него ключ TEXT, а не вложенность. Но платить
там за путь — несколько уровней из 127, и второго правила это не стоит.
Отклонив запись, отчёт скажет, куда ушёл бюджет: сколько уровней забрал путь, сколько осталось значению и сколько этот store читает всего.
Два предела, которые можно задать
Заголовок раздела «Два предела, которые можно задать»Оба идут через limits, и поднять потолок ни один из них не может — только
опустить.
let store = StoreBuilder::new(settings) .limits(|l| l.key_depth(4)) .build()?;
let deep = StorePath::from_segments(["a", "b", "c", "d", "e"]);
if let Err(refused) = store.set(&deep, &1u32) { println!("{refused:?}");}key_depth отклоняет путь, у которого уровней больше, ещё до того, как значение
успеют закодировать. Пригодится там, где пути складываются в рантайме: баг с
неограниченной вложенностью иначе всплывёт нечитаемым файлом, и сильно позже.
let store = StoreBuilder::new(settings) .limits(|l| l.portable_across([default_backend()])) .build()?;portable_across называет движки, на которых содержимое должно остаться
читаемым, — помимо того, что работает на самом деле, — и опускает потолок до
самого низкого из названных. Store на redb, назвавший RON, читает 64 уровня, а
не 512: значение, слишком глубокое для файла, куда его потом выгрузят,
отклонят сейчас, а не после выгрузки.
Глубиной дело не кончается. Названные движки опускают ещё шесть свойств, и каждое считается по одному правилу: держат его и работающий движок, и все названные, — иначе запись отклонят.
| свойство | кто его теряет |
|---|---|
NaN и бесконечности |
JSON и SQLite: у формата нет для них написания, и записанное читается обратно как null |
| enum-варианты | RON: его дерево значений их не знает, см. ron#122. Тип, который для человека пишет себя строкой, — например, IpAddr, — проверяют в этом виде |
Some(None) |
все, кроме RON: вложенный Option схлопывается в None |
u128 и i128 с любым значением |
TOML и RON: у них нет 128-битного целого |
целые шире i64 |
TOML: там для них нет места |
целые, которых не вмещает ни i64, ни u64 |
JSON: число читается обратно как i64, u64 или f64 |
Работающий движок входит в расчёт наравне с названными, и по той же причине,
что и его потолок: значение, которого его кодек не прочитает обратно, потеряно
при любых настройках. Поэтому NaN не примет store на JSON, даже если не
называть никого.
Движки называют поимённо, а не пишут все, и это намеренно. «Все» — цель подвижная: добавили движок, и она уехала под ногами. А настоящее требование обычно уже — приложению, которое ставит JSON на десктопе и SQLite на телефоне, нужны эти два, а про RON у него мнения нет.
Чего это не покрывает
Заголовок раздела «Чего это не покрывает»Store следит за глубиной и за шестью свойствами выше: запись, которая нарушает одно из них, он отклоняет. Что формат способен выразить — вопрос отдельный, и настройками его не сдвинуть: Выбор движка.