Перейти к содержимому

Что здесь значит serde

Библиотека хранит всё, что донесёт serde, — а значит, тип для хранения написан сразу на двух языках. Эта страница про то, где между ними проходит граница, а проходит она в одном месте: сериализуется тип или нет.

Структура, чьи поля — пути (с #[amethystate] или встроенная через nested) не сериализуется никогда. Не «её никто не сериализует», а Serialize для неё не пишется: это не значение, а описание того, где лежат другие значения. Каждое её поле уходит в store само по себе, своим путём и в свой черёд.

Поэтому #[serde(..)], написанный на ней или на её поле, — ошибка компиляции. И притом не наша, а rustc:

error: cannot find attribute `serde` in this scope
= note: `serde` is an attribute that can be used by the derive macros
`Deserialize` and `Serialize`, you might be missing a `derive` attribute

Этим всё и исчерпано. Макрос не читает serde, не разбирает его по пунктам и ничего про него не объясняет: он переносит на генерируемое поле всякий атрибут, которого не понимает, — и судит атрибут тот, кто его понимает. Нет вывода — нет и атрибута.

Лист — всё, что лежит одним значением по одному пути, — наоборот. Store отдаёт его кодеку целиком, забирает байты и внутрь не смотрит, и любой serde-атрибут значит там ровно то же, что везде.

У пяти вещей, ради которых обычно берут serde, здесь есть своё место. Написание разведено потому, что операции разные:

у serde здесь
rename = ".." #[amestate(path = "..")] — и точка в нём уровень, тогда как у serde это одно имя, в котором случилась точка
rename_all = ".." rename_all в #[amethystate(..)] сверху
default на поле #[amestate(default = ..)], берущий выражение, а не путь к функции
flatten #[amestate(flatten)] на nested-поле — и он двигает сегменты на диске, тогда как у serde вливает карту в карту
with, serialize_with, deserialize_with #[amestate(with = ..)] и его половины, теми же функциями

Остальному словарю serde здесь делать нечего. deny_unknown_fields, tag, untagged, transparent, from, remote, skip, alias, getter, borrow — все они описывают одно закодированное значение, а его нет: поля лежат отдельными путями и отсутствуют каждое само по себе. За путь, который не читается, отвечает on_unreadable; поле, которое вообще не попадает в store, — это #[amestate(volatile)]; данные, записанные под старым именем, переносит шаг #[migrate] с #[rename(old => new)] — один раз при открытии и на том конец, тогда как алиас пришлось бы держать вечно.

Всё это описано там же, где живёт словарь: Поля.

Ничего из написанного выше здесь не действует. Store кладёт значение целиком по одному пути и читает его обратно, а что ему сказали по дороге — дело ваше и serde:

#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct Endpoint {
pub host_name: String,
#[serde(default)]
pub port: u16,
}
#[amethystate(prefix = "svc")]
pub struct Service {
#[amestate(default = Endpoint { host_name: "localhost".to_string(), port: 443 })]
pub upstream: Endpoint,
}

Endpoint ложится целиком в svc.upstream — в kebab-case и строгим к неизвестным полям, — и у store на этот счёт мнения нет.

Две вещи всё же чего-то стоят, и стоит знать, каких:

Переименование внутри листа — миграция, которой здесь никто не заметит. Байты переехали, имя в Rust нет. В store, записанном старой сборкой, внутри значения лежит старое имя поля, и новая сборка его не найдёт.

deny_unknown_fields превращает каждое поле, добавленное позже, в жёсткий отказ чтения для старой сборки. proto3 выбрасывал неизвестные поля в первом релизе и передумал в 3.5.0 ровно поэтому. Значение ваше, и решение ваше.

И две привычки, которые стоит держать — по той же причине, по какой их держит любой формат:

#[serde(default)] на каждом поле, добавленном после первого релиза. Serde и так пропускает поле, которого не знает, поэтому значение, записанное новой сборкой, читается старой даром. В обратную сторону даром не выходит: полю, которому нечем заполниться, нечего и подставить — оно валит всё значение целиком, и лист, записанный до появления поля, перестаёт читаться.

Именованные поля, а не кортеж. Кортежная структура сериализуется по позициям, так что поле, вставленное в середину, молча сдвигает все следующие.

Что формат вообще донесёт — другой список

Заголовок раздела «Что формат вообще донесёт — другой список»

Ничего из этого не про то, что движок способен удержать. Это измерено и лежит в Выборе движка: ron не понесёт enum, json и sqlite не понесут NaN, у toml нет места дальше i64. Там отказывают значению, а эта страница — про тип, который говорит то, чего store не может записать.

Тип может пройти один список и не пройти другой.