Динамическая отправка с чертами, которые имеют универсальные методы - PullRequest
0 голосов
/ 25 января 2019

У меня есть сценарий использования, аналогичный описанному здесь , но он немного отличается тем, что мое решение не может заменить универсальный метод неуниверсальным методом. Вот код, который у меня есть ( Rust Playground ):

use serde::{de::DeserializeOwned, Serialize};
use serde_json;

trait Serializer { 
    fn serialize_data<V>(&self, data: &V) -> Result<String, String> where V: Serialize;

    fn deserialize_data<V>(&self, ser_data: &str) -> Option<V> where V: DeserializeOwned;
}

struct JsonSerializer { 
    x: i32 // some member I need to store
}

impl JsonSerializer {
    fn new() -> JsonSerializer {
        JsonSerializer { x: 1 }
    }
}

impl Serializer for JsonSerializer {
    fn serialize_data<V>(&self, data: &V) -> Result<String, String> where V: Serialize {
        match serde_json::to_string(data) {
            Ok(ser_data) => Ok(ser_data),
            Err(err) => Err(err.to_string())        
        }
    }

    fn deserialize_data<V>(&self, ser_data: &str) -> Option<V> where V: DeserializeOwned {
        match serde_json::from_str(ser_data).unwrap() {
            Ok(val) => Some(val),
            Err(_) => None
        }
    }
}



// I may want to have more serializer objects like 
// YamlSerizlier, BincodeSerializer and so on...
// ...

struct MyMainObject {
    serializer: Box<Serializer>
}

impl MyMainObject {
    fn new() -> MyMainObject {
        MyMainObject { serializer: Box::new(JsonSerializer::new()) }
    }

    fn do_something(&self) {
        println!("{}", self.serializer.serialize_data(&1));
        println!("{}", self.serializer.serialize_data(&String::from("MY STRING")));
    }
}

fn main() {
    let my_main_object = MyMainObject::new();
    my_main_object.do_something();
}

Как описано в предыдущем вопросе, при компиляции этого кода я получаю ошибку the trait `Serializer` cannot be made into an object, потому что у него есть общие методы:

   Compiling playground v0.0.1 (/playground)
error[E0038]: the trait `Serializer` cannot be made into an object
  --> src/main.rs:42:5
   |
42 |     serializer: Box<Serializer>
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Serializer` cannot be made into an object
   |
   = note: method `serialize_data` has generic type parameters
   = note: method `deserialize_data` has generic type parameters

Но в моем случае я хочу, чтобы эти методы оставались общими, чтобы я мог сериализовать / десериализовать данные любого типа.

Поэтому мой вопрос заключается в том, как сохранить шаблон динамической диспетчеризации и при этом заставить его работать, а это означает, что я хочу использовать элемент Serializer в MyMainObject, который можно инициализировать с любым типом объекта сериализатора (Json, Yaml и т. Д.). .), а затем позвоните serializer.serialize_data() или serializer.deserialize_data() внутри MyMainObject.

Если это невозможно, какую лучшую альтернативу вы можете предложить?

EDIT:

Мне нужно решение, которое бы работало для сериализаторов различного типа, перечислим следующие:

Ответы [ 2 ]

0 голосов
/ 01 февраля 2019

Примечание

Следующее не является хорошим долгосрочным решением, это просто обходной путь. Правильный способ сделать то, что вы хотите, - это найти и реализовать метод для согласования bincode и serde_yaml с erased_serde. Но если вам нужно, чтобы оно работало прямо сейчас, вот

Суть

По сути, вы можете использовать enums для написания динамической отправки бедняку. Это выглядит примерно так (я упростил и опустил некоторые вещи):

struct JsonSerializer();
struct YamlSerializer();

trait Serializer {
    fn serialize<V>(&self, thing: &V) -> ();
}

impl Serializer for JsonSerializer {
    fn serialize<V>(&self, thing: &V) -> () {
        println!("json");
    }
}

impl Serializer for YamlSerializer {
    fn serialize<V>(&self, thing: &V) -> () {
        println!("yaml");
    }
}

// That's what we'll be using instead of Box<dyn Serializer>
enum SomeSerializer {
    Json(JsonSerializer),
    Yaml(YamlSerializer),
}

impl SomeSerializer {
    pub fn serialize<V>(&self, thing: &V) -> () {
        match self {
            SomeSerializer::Json(ser) => ser.serialize(thing),
            SomeSerializer::Yaml(ser) => ser.serialize(thing),
        }
    }
}

Вот как вы можете его использовать (за исключением того, что вам, вероятно, понадобятся реальные функции конструктора):

pub fn main() {
    let thing = 2;
    let json = SomeSerializer::Json(JsonSerializer());
    let yaml = SomeSerializer::Yaml(YamlSerializer());
    json.serialize(&thing);
    yaml.serialize(&yaml);
}

Это имеет серьезные недостатки (см. Ниже), но позволяет упаковывать что-то, имеющее универсальные методы, в унифицированный интерфейс.

проблемы

Основная проблема этого подхода заключается в том, что трудно добавить новые сериализаторы в настройку. С Box<dyn Serializer> все, что вам нужно сделать, это impl Serializer для чего-то. Здесь вы должны добавить вариант в enum и сопоставление с образцом во всех соответствующих методах. Это неудобно в ящике, где определено SomeSerializer, а невозможно в других ящиках. Кроме того, добавление варианта к общедоступному перечислению является серьезным изменением, которое в нижних ящиках может не приветствоваться. Есть способы улучшить это до некоторой степени:

Скрыть SomeSerializer

На самом деле SomeSerializer не имеет смысла быть публичным. Возможность сопоставления с шаблоном имеет очень мало преимуществ, и это общедоступность ограничивает то, что вы можете сделать с ним, не нарушая ситуацию ниже по течению. Обычное решение состоит в том, чтобы поместить его в непрозрачную структуру и экспортировать ее, оставив само перечисление скрытым:

pub struct VisibleSerializer(SomeSerializer);

Все еще используйте черту

Вы не можете расширить SomeSerializer дополнительными сериализаторами в других ящиках. Вы можете продолжить монтировать на нем больше слоев enum (и это прискорбно и уродливо), но тогда ни одна функция в исходном ящике не примет такую ​​конструкцию. В этом можно помочь: вместо того, чтобы делать serialize неотъемлемым методом SomeSerializer, внедрить для него Serializer и сделать все функции, которые будут использовать SomeSerializer универсальными, и принять T: Serializer. Внезапно все нижестоящие ящики могут добавить желаемый сериализатор в настройку.

Особые случаи только особые случаи

Наличие более трех из четырех сериализаторов, упакованных таким образом, довольно смешно, не говоря уже о неловкости. Однако, если большинство сериализаторов, с которыми вы хотите работать, на самом деле erased_serde -совместимы, вы можете иметь для них вариант всеобъемлющего перечисления в SomeSerializer и иметь отдельные варианты только для несовместимых:

enum SomeSerializer {
    Whatever(Box<dyn erased_serde::Serializer>),
}
0 голосов
/ 25 января 2019

Вы не можете использовать необъектно-безопасные черты с динамической отправкой;правила безопасности объектов конкретно относятся к вещам, которые предотвращают динамическую диспетчеризацию.

Иногда существуют обходные пути для конкретных сценариев.Они обычно сложны.Но для serde, в частности, есть ящик erased_serde, потому что вы не первый с этой проблемой.

...