Kv
Реактивные значения, к которым обращаются по пути, без всякой объявленной структуры. Для набора ключей, который складывается в рантайме, и для случаев, где схема — возни больше, чем задача того стоит.
let kv = store.kv();
kv.set("theme", &"dark".to_string())?;let theme = kv.get::<String>("theme")?;
let ui = kv.namespace("ui");ui.set("width", &800u32)?;let under_ui = ui.keys()?;
kv.remove("theme")?;Имя — это один уровень
Заголовок раздела «Имя — это один уровень»Любое имя здесь занимает один уровень, а вкладывает namespace. Поэтому
kv.set("ui.width", ..) пишет один ключ с именем ui.width — имя, в котором
просто стоит точка, — а kv.namespace("ui").set("width", ..) пишет width
внутри ui.
Точку в имени на диске экранируют, так что это два разных места, разными они и останутся. Перепутать их местами — ошибка, за которой стоит следить: строка с точками выглядит как путь, а путь она не задаёт.
keys() перечисляет всё под тем хендлом, на котором его позвали, — на всех
уровнях ниже и по порядку. Сужает перечисление namespace; корневой хендл
перечислит весь store.
kv.set("theme", &"dark".to_string())?;kv.namespace("ui").set("width", &800u32)?;kv.namespace("ui").namespace("panel").set("left", &true)?;
let from_ui: Vec<String> = kv .namespace("ui") .keys()? .iter() .map(|key| key.to_string()) .collect();
assert_eq!(from_ui, ["ui.panel.left", "ui.width"]);Приходят целые пути, поэтому width внутри ui значится как ui.width, а
корневой theme из хендла ui не значится вовсе.
Ячейки и карты
Заголовок раздела «Ячейки и карты»let width = kv.namespace("ui").cell("width", 800u32)?;let flags = kv.map::<String, bool>("flags")?;
flags.insert("dark".to_string(), &true)?;Приходит обычный
ReactiveCell или ReactiveMap,
поэтому подписки тут работают ровно как у объявленных полей. Тип ячейки берут
из её значения по умолчанию — писать одно и то же дважды не придётся.
Там, где уже живёт структура
Заголовок раздела «Там, где уже живёт структура»Объявленный prefix принадлежит своей структуре, и запись внутрь него через
Kv получит отказ:
let said = match kv.namespace("network").set("port", &"8080".to_string()) { Err(KvWrite::Declared { at, declared_at, by, }) => { format!("{at} lies in {declared_at}, which {by} declared") } other => { other?; "the write went through".to_string() }};
kv.namespace("networkish") .set("port", &"8080".to_string())?;Сам по себе отказ печатается одной строкой:
network.port is declared by NetworkА три факта лежат на варианте: at — путь, куда писали, declared_at — путь,
который объявила схема (он же сам at или тот, внутри которого at лежит), и
by — структура, которая его объявила:
network.port lies in network.port, which Network declarednetworkish при этом не задет: объявление владеет своим путём и тем, что лежит
внутри него, а не всеми именами, которые так же начинаются.
cell или map над тем же местом — это открытие, а не запись, поэтому оттуда
приходит OpenStruct::Taken: то же столкновение, сказанное набором того
вызова, которому оно принадлежит.
Причина — в том, что чужой тип делает со структурой. Положите String туда,
где объявлен u16, — подписка поля это не декодирует, поэтому поле будет
показывать старое значение, пока store держит другое. А следующий запуск просто
упадёт, читая этот путь обратно.
Один путь — один тип
Заголовок раздела «Один путь — один тип»let ui = kv.namespace("ui");
let _width = ui.cell("width", 800u32)?;let refused = ui.cell("width", String::new());Тип здесь нигде не записан. Ячейка читает свой путь, чтобы заполнить себя, — и второй запрос находит то, что положил первый, и декодировать это не может. Ошибка приходит от кодека и называет, что он нашёл.
Выходит, проверяет тут само сохранённое значение, и отсюда следствия, которых у реестра типов не было бы. Проверка переживает перезапуск: то, что записал прошлый прогон, всё ещё лежит и читается. Работает она и на пустом пути: первая же ячейка кладёт туда своё значение по умолчанию. А вот для сырого доступа её нет вовсе:
kv.set("thing", &1u32)?;kv.set("thing", &"now a string".to_string())?; // replaces itkv.get::<u32>("thing")?; // Err, at the readСырой set перезапишет что угодно, какого бы типа оно ни было. Расхождение
всплывёт на следующем get, который попросит прежний тип.
Чем приходится платить
Заголовок раздела «Чем приходится платить»Версии, миграции и поиск дрейфа остаются объявленным структурам. Если у данных есть форма и её собираются развивать — объявите её.