Как вообще можно добавить десериализацию объектов полиморфных признаков в Rust?
Я пытаюсь решить проблему сериализации и десериализации Box<SomeTrait>
, Я знаю, что в случае иерархии закрытого типа, рекомендуемый способ - использовать enum, и нет проблем с их сериализацией, но в моем случае использование enums - неуместное решение.
Сначала я попытался использовать Serde, поскольку это де-факто механизм сериализации Rust. Серде способен на сериализацию Box<X>
но не в том случае, когда X
это черта. Serialize
trait не может быть реализован для объектов trait, потому что он имеет универсальные методы. Эта конкретная проблема может быть решена с помощью стертого serde, поэтому сериализация Box<SomeTrait>
может работать.
Основная проблема - десериализация. Чтобы десериализовать полиморфный тип, вам нужно иметь маркер типа в сериализованных данных. Этот маркер должен быть сначала десериализован, а затем использован для динамического получения функции, которая будет возвращать Box<SomeTrait>
,
std::any::TypeId
может использоваться в качестве типа маркера, но главная проблема заключается в том, как динамически получить функцию десериализации. Я не рассматриваю возможность регистрации функции для каждого полиморфного типа, которая должна вызываться вручную во время инициализации приложения.
Я знаю два возможных способа сделать это:
- Языки с отражением во время выполнения, такие как C#, могут использовать его для получения метода десериализации.
- В C++ библиотека cereal использует магию статических объектов для регистрации десериализатора в статической карте во время инициализации библиотеки.
Но ни один из этих вариантов не доступен в Rust. Как можно добавить десериализацию полиморфных объектов в Rust?
2 ответа
Это реализовано dtolnay.
Идея довольно умная и объясняется в README
:
Как это работает?
Мы используем
inventory
ящик для создания реестра имплитаций вашей черты, который построен наctor
ящик для подключения функций инициализации, которые вставляются в реестр. ПервыйBox<dyn Trait>
десериализация выполнит работу по повторению реестра и построению карты тегов для функций десериализации. Последующие десериализации находят правильную функцию десериализации на этой карте. Вerased-serde
crate также задействован, чтобы сделать все это таким образом, чтобы не нарушить безопасность объекта.
Подводя итог, можно сказать, что каждая реализация признака, объявленного как [де] сериализуемый, регистрируется во время компиляции, и это разрешается во время выполнения в случае [де] сериализации объекта признака.
Все ваши библиотеки могут обеспечить процедуру регистрации, охраняемую std::sync::Once
, что прописать какой-то идентификатор в общий static mut
, но, очевидно, ваша программа должна вызывать их всех.
Я понятия не имею, если TypeId
выдает согласованные значения при перекомпиляции с разными зависимостями.
Библиотека для этого должна быть возможной. Чтобы создать такую библиотеку, мы должны создать двунаправленное отображение из TypeId на имя типа перед использованием библиотеки, а затем использовать его для сериализации / десериализации с маркером типа. Можно было бы иметь функцию для регистрации типов, которые не принадлежат вашему пакету, и предоставлять макро-аннотацию, которая автоматически делает это для типов, объявленных в вашем пакете.
Если есть способ получить доступ к идентификатору типа в макросе, это было бы хорошим способом для отображения соответствия между TypeId и именем типа во время компиляции, а не во время выполнения.