Кто владеет каким местом
prefix структуры говорит, где она сидит, а поля её — пути под этим префиксом.
Написать одно и то же место двум структурам ничто не мешает: ключ с точками
достаёт так же глубоко, как и префикс с точками.
#[amethystate(prefix = "ui", version = 1)]pub struct Ui { #[amestate(path = "panels.left.visible", default = true)] pub left_panel_visible: bool,}
#[amethystate(prefix = "ui.panels", version = 1)]pub struct Panels { #[amestate(path = "left.visible", default = true)] pub left_visible: bool,}Сложите префикс с ключом, и обе упрутся в один путь: ui.panels.left.visible.
Вторая получает отказ
Заголовок раздела «Вторая получает отказ»Store держит таблицу занятого, и вторая структура, открывающаяся над тем же местом, не построится:
let _ui = Ui::new_with(&store);
let refused = Panels::try_new_with(&store) .expect_err("`ui.panels.left.visible` is spelled by both of them");
let OpenStruct::Taken(taken) = &refused else { panic!("{refused}")};
let Taken { at, wanted_by, held_at, held_by,} = &**taken;
println!("{wanted_by} wants {at}, which {held_by} already holds at {held_at}");Ошибка — OpenStruct::Taken, и она называет обе стороны по обоим путям:
at и wanted_by — у объявления, которому отказали, held_at и held_by — у
того, что стоит. Пути расходятся, когда одно объявление достаёт другое через
предка — root.b держит root.b.x, — и эта пара и есть весь диагноз.
Ничего не надо выуживать из напечатанного текста. Taken — один из шести
вариантов, которыми отвечает конструктор, и match без ветки _ обязан его
назвать. Остальные пять — в Ошибках.
Столкновение это кончается двумя способами, и отказ — дешёвый из них. Дорогой выглядит так: две структуры пишут в один путь, одна на каждом сохранении молча затирает значение другой, а чтобы это найти, надо прочесть два объявления, которые друг друга не поминают. Отказ приходит на вызове, который открывает структуру, и называет обе.
Что считается пересечением
Заголовок раздела «Что считается пересечением»Два места пересекаются, когда одно держит другое. В этом всё правило, и оно симметрично: неважно, какое объявлено первым, важно только, что одно поддерево начинает другое.
| два места | пересекаются |
|---|---|
ui и ui.panels |
да — второе внутри первого |
ui.panels и ui |
да — та же пара, только наоборот |
ui.panels и ui.status |
нет |
ui и ui!x |
нет — ui!x другое имя, просто начинается с тех же букв |
На поле чужой структуры префикс тоже не ляжет. Поле — такое же место, как всякое
другое, поэтому структура с префиксом root.b получит отказ, если поле b под
root уже объявила другая.
Места, которые не встречаются, никто не трогает, как бы близко они ни сидели:
#[amethystate(prefix = "ui.panels.right", version = 1)]pub struct RightPanel { #[amestate(default = true)] pub visible: bool,}Ближе уже некуда: Ui держит ui.panels.left.visible, RightPanel —
ui.panels.right.visible. Расходятся они на последнем общем уровне, ни один
путь не начинает другой, и обе открываются в одном store. А вот Panels,
которая метила в ui.panels, получала отказ — её место начинало место Ui.
Кто чем владеет
Заголовок раздела «Кто чем владеет»Три правила — из них следует всё, что выше.
Лист владеет своим путём и всем, что под ним, а держать под ним нечего. Поддерево ему не нужно. Владеет он им затем, чтобы никто не залез под скаляр.
Карта владеет своим путём и всем, что под ним, а занимает один уровень. Ключи у неё появляются на ходу, объявить их заранее некому. Будь место под картой свободным, за каждый ещё не написанный ключ можно было бы спорить.
Структура своим префиксом не владеет — владеют её поля. Пути у неё все объявлены, а до необъявленного ей дела нет: уровень, на котором она стоит, остаётся свободным.
Вот эту разницу и стоит запомнить. Одно объявление:
#[amethystate(prefix = "ui")]pub struct Panels { #[amestate(default = {})] pub open: ReactiveMap<String, u32>,
#[amestate(default = "dark".to_string())] pub theme: String,}и что после него остаётся свободным:
ui · свободен — префикс никто не занимает├─ theme ▪ объявил Panels; ниже ничего не бывает├─ open ▪ объявил Panels, картой│ ├─ left ▪ запись, имя ей дали на ходу│ └─ right ▪ запись└─ myplugin · свободен — сюда может писать расширениеui.myplugin свободен: ui никто не занимал. ui.open.myplugin — нет:
ui.open занят.
под префиксом структуры под префиксом карты ui ui.open ├─ theme объявлено ├─ left запись ├─ open объявлено ├─ right запись └─ myplugin можно └─ myplugin отказ«Карта владеет всем поддеревом» и «карта занимает один уровень» — два разных
утверждения, и верны оба. ui.open.left — запись карты. ui.open.left.px
попадает в её поддерево, но записью не является, — отказ приходит именно
поэтому: место не ничьё, просто карта до него не достаёт.
Глубина значения — не глубина store. Запись хранит свой тип целиком, закодированным, по одному пути: структура на пять уровней вглубь — это один ключ, а не пять уровней.
tree.roots.one ▪ один ключ, одно хранимое значение { name: "a", children: [ { … }, { … } ] } что там внутри — дело кодекаЗанятое место переживает хендл
Заголовок раздела «Занятое место переживает хендл»Дроп структуры её место не освобождает. Таблица принадлежит store и живёт ровно столько же, сколько он.
Так задумано. Освобождай место дроп — и отказ зависел бы от того, когда
значение вышло из области видимости: одна и та же программа то открывалась бы
чисто, то падала, смотря по тому, где кончилась привязка let.
Как спросить, у кого место
Заголовок раздела «Как спросить, у кого место»let field = StorePath::parse_joined("ui.panels.left.visible")?;let owner = store.places().declared_by(&field);
println!("{owner:?}");Ищут по точному пути, а записаны те пути, которые заняты на самом деле:
собственный путь поля, а не префикс, под которым оно сидит. Поэтому Ui выше
находится по ui.panels.left.visible, а не по ui.
Отвечает он именем схемы — тем самым, которое печатает отказ.