Как можно десериализовать объекты полиморфных признаков в Rust, если вообще?

Я пытаюсь решить проблему сериализации и десериализации Box<SomeTrait>. Я знаю, что в случае иерархии закрытого типа рекомендуемым способом является использование перечисления, и нет никаких проблем с их сериализацией, но в моем случае использование перечислений является неприемлемым решением.

Сначала я попытался использовать Serde, поскольку он является механизмом сериализации де-факто Rust. Serde способен сериализовать Box<X>, но не в том случае, когда X является признаком. Функция Serialize может быть реализована для объектов-признаков, поскольку она имеет общие методы. Эта конкретная проблема может быть решена с помощью erased-serde, поэтому сериализация Box<SomeTrait> может работать.

Основная проблема - десериализация. Чтобы десериализовать полиморфный тип, вам необходимо иметь маркер определенного типа в сериализованных данных. Сначала этот маркер следует десериализовать, а затем использовать для динамического получения функции, которая вернет Box<SomeTrait>.

std::any::TypeId может использоваться как тип маркера, но главная проблема заключается в том, как динамически получить функцию десериализации. Я не рассматриваю возможность регистрации функции для каждого полиморфного типа, который должен быть вызван вручную во время инициализации приложения.

Я знаю два возможных способа сделать это:

  • Языки, которые имеют отражение во время выполнения, например С#, могут использовать его для получения десериализации.
  • В С++ библиотека зерновых использует магию статических объектов для регистрации десериализатора в статической карте во время инициализации библиотеки.

Но ни один из этих вариантов не доступен в Rust. Как можно десериализовать полиморфные объекты в Rust, если вообще?

Ответ 1

Это было реализовано dtolnay.

Концепция довольно умная и объяснена в README:

Как это работает?

Мы используем inventory ящик для создания реестра значений вашей черты, который построен на ctor ящике для подключения функций инициализации, которые вставляются в реестр. Первая десериализация Box<dyn Trait> будет выполнять итерацию реестра и создание карты тегов для функций десериализации. Последующие десериализации находят правильную функцию десериализации на этой карте. erased-serde ящик также используется, чтобы сделать все это таким образом, чтобы не нарушить безопасность объекта.

Подводя итог, можно сказать, что каждая реализация признака, объявленного как [de] serializable, регистрируется во время компиляции, и это разрешается во время выполнения в случае [de] сериализации объекта признака.

Ответ 2

Все ваши библиотеки могут предоставить процедуру регистрации, охраняемую std::sync::Once, которая регистрирует некоторый идентификатор в общем static mut, но очевидно, ваша программа должна называть их всех.

Я понятия не имею, если TypeId дает согласованные значения для перекомпиляций с разными зависимостями.

Ответ 3

Библиотека для этого должна быть возможной. Чтобы создать такую библиотеку, мы должны создать двунаправленное отображение из TypeId для ввода имени перед использованием библиотеки, а затем использовать его для сериализации/десериализации с маркером типа. Можно было бы иметь функцию для регистрации типов, которые не принадлежат вашему пакету, и предоставлять макро-аннотацию, которая автоматически делает это для типов, объявленных в вашем пакете.

Если есть способ получить доступ к идентификатору типа в макросе, это было бы хорошим способом для отображения соответствия между TypeId и именем типа во время компиляции, а не во время выполнения.