Поля
Атрибуты поля необязательны. Поле без #[amestate] берёт значением
Default::default(), а ключом — своё имя.
#[amethystate(prefix = "app")]pub struct AppState { pub counter: u32, // no annotation — uses Default::default(), stored as "app.counter"
#[amestate(default = 8080)] pub port: u16,}| Атрибут | Тип | Описание |
|---|---|---|
path |
&str |
Где поле лежит, если это не его собственное имя. Точка в нём — уровень, так что поле можно опустить куда угодно под префикс, а не только переименовать. |
default |
Expr |
Значение на первом запуске. Без него — Default::default(). |
nested |
флаг | Поле само — структура #[amethystate]. |
flatten |
флаг | На nested-поле: его поля лежат на этом уровне, без сегмента с его именем. |
volatile |
флаг | Живёт только в памяти: store его не читает и не пишет, на каждом запуске оно равно изначально заданному. |
with, serialize_with, deserialize_with |
путь | Функции, которыми поле пишут и читают, когда это делает не его собственный тип. |
on_unreadable |
вариант | Что делать со значением, которое не декодируется, — вместо того, что сказала структура. У карты нет умолчания на одну запись, поэтому здесь это ошибка компиляции, и она называет unreadable_entries. См. Когда значение не читается. |
on_delete |
вариант | Что делать с полем, у которого удалили ключ. На карте речь про её уровень, а не про записи: убрать запись — это убрать запись при любом ответе. А когда уходит сам уровень, UseDefault кладёт объявленные записи обратно, Keep (по умолчанию) оставляет карту пустой. |
unreadable_entries |
вариант | Только на карте: Skip оставляет нечитаемую запись за бортом и строит остальное, Refuse (по умолчанию) отказывает и называет запись. |
rule |
fn |
Правило, которое обязано пройти каждое значение из store или из записи, на каждом входе и выходе: прочитанное, принесённое правкой или записанное; объявленное значение по умолчанию его не проходит. Что оно поправило, доходит и до поля, и до store. См. Правила. |
Их можно писать в одном #[amestate(..)] или раскидать по нескольким — как
читается лучше:
#[amestate(path = "panels.left.visible")]#[amestate(default = true, on_delete = Keep)]pub left_panel_visible: bool,Сказать что-то из них дважды — ошибка компиляции с этим именем: победило бы второе, а первое выглядело бы прочитанным.
Макрос сверяет каждый атрибут и на незнакомом валит компиляцию, перечислив те, что знает, — так что опечатка станет ошибкой, а не молчаливо ничем.
Атрибуты, которые не этого макроса
Заголовок раздела «Атрибуты, которые не этого макроса»Всё остальное, написанное на поле, переносится на поле, которое макрос
генерирует, и на его геттер. Док-комментарий доезжает туда, куда его целили;
#[allow] и #[deprecated] делают, что сказано; а атрибут, которого здесь
никто не понимает, судит тот, кто понимает, — так #[serde(..)] и становится
собственной ошибкой rustc про атрибут, которого нет в области видимости, а не
чем-то, о чём у этого макроса есть мнение.
Исключение — #[cfg], и он отвергается. Поле появляется в сгенерированном коде
в десятке мест: структура, её конструктор, снапшот, схема, которая ложится на
диск, — и часть из них массивы const, где элемент не может быть условным.
Перенести в те места, где можно, и не перенести в остальные — значит собрать
структуру без поля и схему с полем, и никто об этом не скажет. Ставьте cfg на
всю структуру целиком или оставьте поле и решайте в рантайме, что оно держит.
Где лежит поле
Заголовок раздела «Где лежит поле»path называет место, где поле лежит, а rename_all на структуре говорит это
один раз за все поля. Точка в path — уровень, так что поле можно не только
переименовать, но и опустить куда угодно под префикс:
#[amethystate(prefix = "net", rename_all = "camelCase")]pub struct NetState { #[amestate(default = 8080u16)] pub listen_port: u16,
#[amestate(path = "tls.enabled", default = false)] pub tls: bool,}Получается net.listenPort и net.tls.enabled.
Поле, чьи пути лежат на уровне хозяина
Заголовок раздела «Поле, чьи пути лежат на уровне хозяина»flatten на nested-поле говорит: его поля лежат здесь, без сегмента с его
именем.
#[amethystate(prefix = "editor")]pub struct Editor { #[amestate(nested, flatten)] pub window: Window,}Получается editor.width, а не editor.window.width.
Если два таких поля дотягиваются до одного места — ошибка компиляции, и она
называет оба: каждое кладёт своё на этот уровень, и они писали бы друг поверх
друга. То же, если такое поле дотянулось до места поля, лежащего рядом. Места
сравниваются по уровням, а место поля — это его путь и всё, что под ним:
path = "window.width" рядом с таким полем, внутри которого лежит структура
window, — тоже ошибка, хотя ни одно имя целиком не совпало.
И path, и flatten решают, куда лягут данные, поэтому поменять любой из них
в том, что уже выпущено, — миграция. Данные остаются там, куда их положила
старая сборка, новая смотрит в другое место, а человек видит, что настройки
сбросились.
Поле, которое лежит иначе
Заголовок раздела «Поле, которое лежит иначе»Когда собственная кодировка типа не та, которую вы хотите видеть на диске, поле пишут и читают своей парой функций:
#[amestate(with = since_the_epoch)]pub opened: SystemTime,with = m — это m::serialize, которая пишет, и m::deserialize, которая
читает. Половину можно назвать и отдельно: serialize_with или
deserialize_with. Тогда вторую половину делает сам тип — значение уйдёт на
диск одним способом, а вернётся другим. Обычно это ошибка, так что пишите обе,
если вам не нужно ровно такое расхождение.
По дороге на диск значения больше не касается никто. На пути лежит то, что написала первая функция, и превращает это обратно только вторая.
Но сам тип всё равно должен реализовывать Serialize и Deserialize. Этого
требует тип любого поля, а with выбирает форму на диске и их не заменяет. Тип
из чужого крейта, у которого нет ни того ни другого, заворачивают в свою обёртку
(newtype) и реализуют оба трейта на ней — хоть той же парой функций, если нужна
именно такая форма.
Что из словаря serde сюда не переходит и почему: Что здесь значит serde.
Поля volatile
Заголовок раздела «Поля volatile»volatile-поле живёт только в памяти и на каждом запуске равно изначально
заданному. Годится для сиюминутного состояния интерфейса, которое хранить
незачем.
#[amethystate(prefix = "app")]pub struct AppState { #[amestate(default = 8080)] pub port: u16,
#[amestate(default = false, volatile)] pub loading: bool, // always starts as false, never written to disk}Правило volatile-поле берёт всё равно: каждая
запись идёт через него, и оно держит в рамках значение, которое процесс хранит
в памяти и никуда не кладёт.