【问题标题】:Dynamic dispatch with traits that have generic methods具有通用方法的特征的动态调度
【发布时间】:2019-01-25 06:54:04
【问题描述】:

我有一个与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();
}

如上一个问题所述,编译此代码时出现错误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

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

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

如果这不可能,您可以建议的最佳选择是什么?

编辑:

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

【问题讨论】:

  • This library 不久前在 Rust 的 subreddit 上宣布。我没有仔细研究它,但它可能是你想要的
  • 如果我理解正确的话,这个库可以实现特征对象的序列化/反序列化,但我正在寻找的是一种在相同特征下实现不同序列化程序的方法,或者至少是接近于的设计那
  • 哦,对了,应该更仔细地阅读这个问题,抱歉)。嗯,总是有 enum 方法,但这是很多样板文件,而且可扩展性不是很好。
  • 你能详细说明一下这个选项吗?
  • 将所有序列化程序包装在一个枚举中,并使用枚举而不是Box&lt;Serializer&gt;。主要的缺点是您失去了 trait 对象提供的灵活性 - 每当您想要添加对新序列化程序的支持时,您需要向枚举添加一个变体,并且您的库的用户(如果它是一个)可以' t 只是插入他们自己的序列化程序而没有类似的解决方法。但它适用于一般的东西。

标签: generics rust


【解决方案1】:

注意

以下不是一个好的长期解决方案,它只是一种解决方法。做你想做的事的正确方法是找出并实现一种方法来协调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);
}

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

问题

这种方法的主要问题是很难将新的序列化程序添加到设置中。使用Box&lt;dyn Serializer&gt;,您需要做的就是通过impl Serializer 获取某些信息。在这里,您必须在所有相关方法中向枚举和模式匹配添加一个变体。这在定义 SomeSerializer 的包中很不方便,而在其他包中不可能。此外,向公共枚举添加变体是一项重大更改,下游的 crate 可能并不完全欢迎。有一些方法可以在一定程度上改善这种情况:

隐藏SomeSerializer

SomeSerializer 公开没有任何意义。对它进行模式匹配的能力几乎没有什么好处,而且它是公开的,它限制了你可以对它做的事情而不会破坏下游的东西。通常的解决方案是把它放在一个不透明的结构中并导出它,让枚举本身隐藏起来:

pub struct VisibleSerializer(SomeSerializer);

仍然使用特征

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

特殊情况仅特殊情况

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

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

【讨论】:

    【解决方案2】:

    您不能在动态调度中使用非对象安全的特征;对象安全规则专门针对阻止动态调度的事物。

    有时会有针对特定场景的解决方法。它们通常很复杂。但是对于serde,特别是erased_serde crate,因为你不是第一个遇到这个问题的人。

    【讨论】:

    • 谢谢!这个库也可以与bincodeserde_yaml 一起使用吗?
    • @fx23 不知道,我自己没用过。但我认为没有理由不这样做。
    • serde_json 和 serde_cbor 包含一个实现 serde::ser::Serializer 特征的 Serializer 对象。 erased_serde 依赖于此。但是 bincode 和 serde_yaml 没有这个对象,所以我认为它们不能与 erased_serde 一起使用
    • 我假设你是在 erased_serde 上打开错误的人。您可能对这些问题感兴趣:github.com/dtolnay/serde-yaml/issues/44github.com/TyOverby/bincode/issues/242
    • 谢谢!显然这个库不能与bincodeserde_yaml 一起使用。我知道这种设计不适用于 Rust。你能推荐一个尽可能接近我想要的替代设计吗?
    猜你喜欢
    • 1970-01-01
    • 2017-06-24
    • 1970-01-01
    • 2016-08-11
    • 2022-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多