generics - 具有通用方法的特征的动态调度

标签 generics rust

我有一个与描述的类似的用例 here ,但略有不同,因为我的解决方案无法用非泛型方法替换泛型方法。这是我的代码(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();
}

如上一个问题所述,编译此代码时出现错误特征 `Serializer` 无法制作为对象,因为它具有通用方法:

   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

但就我而言,我希望这些方法保持通用,以便我可以序列化/反序列化任何类型的数据。

所以我的问题是如何保持动态调度模式并仍然使其工作,这意味着我想要 MyMainObject 中的 Serializer 特征成员,我可以使用任何初始化序列化器对象的类型(Json、Yaml 等),然后在 MyMainObject 内部调用 serializer.serialize_data()serializer.deserialize_data() >.

如果这不可能,您可以建议的最佳替代方案是什么?

编辑:

我需要一个适用于不同类型序列化器的解决方案,列出这些序列化器:

最佳答案

注意

以下并不是一个好的长期解决方案,它只是一个解决方法。做你想做的事情的正确方法是找出并实现一种协调方法 bincodeserde_yamlerased_serde 。但如果您现在需要它工作,这里是

要点

基本上,您可以使用枚举来编写穷人的动态调度。它看起来或多或少像这样(我简化并省略了一些东西):

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);
}

这有严重的缺点(见下文),但它确实允许您将具有通用方法的东西打包到统一的接口(interface)中。

问题

这种方法的主要问题是很难向设置中添加新的序列化器。与Box<dyn Serializer>您需要做的就是impl Serializer为了某件事。在这里,您必须向枚举添加一个变体,并在所有相关方法中对​​其进行模式匹配。这在 SomeSerializer 的 crate 中很不方便。已定义,并且在其他 crate 中不可能。此外,向公共(public)枚举添加变体是一项重大更改,下游 crates 可能并不完全欢迎。有一些方法可以在一定程度上改善这种情况:

隐藏 SomeSerializer

这对于SomeSerializer来说并没有什么意义。公开。在其上进行模式匹配的能力几乎没有什么好处,并且它是公开的,这限制了您在不破坏下游事物的情况下可以对其进行的操作。通常的解决方案是将其放入不透明的结构中并将其导出,从而隐藏枚举本身:

pub struct VisibleSerializer(SomeSerializer);

仍然使用该特征

您无法扩展SomeSerializer在其他 crate 中带有额外的序列化器。您可以继续在其上安装更多枚举层(这既不幸又丑陋),但是原始 crate 中的任何函数都不会接受这种构造。这可以有所帮助:而不是制作 serialize SomeSerializer的固有方法,实现Serializer为它,并创建所有将使用 SomeSerializer 的函数通用并接受 T: Serializer 。突然间,所有下游 crates 都可以在设置中添加他们想要的序列化器。

仅特殊情况特殊情况

以这种方式包装超过四分之三的序列化程序有点荒谬,更不用说尴尬了。但是,如果您想要使用的大多数序列化器实际上是 erased_serde -兼容,您可以在 SomeSerializer 中为它们提供一种包罗万象的枚举变体,并且仅针对不兼容的版本有单独的变体:

enum SomeSerializer {
    Whatever(Box<dyn erased_serde::Serializer>),
}

关于generics - 具有通用方法的特征的动态调度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54360326/

相关文章:

rust - 改变循环中的两个相关值

rust - 在结构中使用枚举会导致 "unresolved name"错误

rust - 由于 Libsodium 在 : pwhash_argon2i and pwhash_argon2id 上失败, cargo 测试不再有效

iterator - 如何将实现特征的类型的迭代器的内容装箱?

c - 为什么当静态没有链接时const在链接过程中消失?

不需要它的Java泛型名称类型参数

Java 泛型语法

c# - 如何获取泛型类的名称?

c# - 如何在 C# 中的泛型接口(interface)上创建协变扩展方法?

java - <? 之间的区别 super T> 和 <?在 Java 中扩展 T>