Как можно десериализовать объекты полиморфных признаков в 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 и именем типа во время компиляции, а не во время выполнения.