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

时间:2019-01-25 06:54:05

标签: generics rust

我有一个与here描述的用例相似的用例,但是有一点不同,因为我的解决方案无法用非泛型方法代替泛型方法。这是我的代码(Rust Playground):

com.example.sourceapp

如上一个问题所述,在编译此代码时,我收到一条错误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特质成员,我可以使用任何类型的序列化程序对象进行初始化(Json ,Yaml等),然后在MyMainObject内部调用serializer.serialize_data()serializer.deserialize_data()

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

编辑:

我需要一种适用于不同类型的序列化器的解决方案,列出以下解决方案:

2 个答案:

答案 0 :(得分:2)

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

某些情况下有时会有变通办法。它们通常很复杂。但是特别是对于serde,有erased_serde条板箱,因为您不是第一个遇到此问题的人。

答案 1 :(得分: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<dyn Serializer>所有你需要做的是impl Serializer的东西。在这里,您必须在所有相关方法中为枚举和模式匹配添加一个变体。这在SomeSerializer被定义板条箱,和不可能在其他包装箱不方便。此外,将变体添加到公共枚举是一项重大变化,下游板条箱可能并不完全受欢迎。有一些方法可以在某种程度上改善这一点:

隐藏SomeSerializer

它并没有真正意义SomeSerializer是公开的。在其上进行模式匹配的能力几乎没有好处,而且公开限制了您可以做的事情而不会破坏下游。通常的解决方案是将其放在不透明的结构中并导出该结构,而使枚举本身隐藏起来:

pub struct VisibleSerializer(SomeSerializer);

仍然使用特征

您不能扩展SomeSerializer与其他包装箱额外串行器。您可以继续在其上安装更多的枚举层(这既不幸又丑陋),但是随后原始板条箱中的任何功能都无法接受这种构造。这可以得到帮助:与其使serializeSomeSerializer的固有方法,不如为其实现Serializer,并使所有将使用SomeSerializer的函数都通用并接受{{ 1}}。突然,所有下游板条箱都可以将所需的序列化器添加到设置中。

仅特殊情况特殊情况

以这种方式包装四个序列化器中的三个以上是荒谬的,更不用说笨拙了。但是,如果您要使用的大多数序列化程序实际上是T: Serializer兼容的,则您可以在erased_serde中为它们使用一种包罗万象的枚举变量,而仅对不兼容的变量有单独的变量一个:

SomeSerializer