Trouble dispatching to overridden specialization trait method in external crate

The overarching issue I'm having stems from the fact that the serde::Serializer trait is modeled around the data model of 19 types, and I'm trying to define a type that has Serializer-specific behaviour and can be shared across crates.

I've gotten pretty far by doing two things:

  1. define a "subclass" trait (or maybe this is like prototypal inheritance?) of a serde::Serializer called Encoder to use in place of a Serializer that defines an additional method encode_link and its default impl by:
    • requiring impls of Encoder to also impl Serializer
    • blanket impl of Encoder for all Serializers, including a default impl for encode_link
    • #[feature(specialization)] to annotate the additional method as overridable
  2. define a public type whose serialize method treats the passed Serializer as an Encoder and calls encode_link, which if not defined by the input Serializer will be just the default method impl
    • this (ideally) would let the type's serialize behaviour be configured by the Serializer/Encoder, rather than require that external crates define their own equivalent Link types with custom serialize impls

In code, this is what it looks like - in crate A:

#![feature(specialization)]

// struct to use in other crates that want to implement an `Encoder` for custom types that include `Link`
struct Link(u8);

impl Serialize for Link {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok> 
    where 
        S: serde::Serializer 
    {
        // allowed due to the blanket impl provided above
        <S as Encoder>::encode_link(serializer, self)
    }
}

trait Encoder: serde::Serializer {
    fn encode_link(self, link: &Link) -> Result<Self::Ok>;
}

impl<T> Encoder for T where T: serde::Serializer {
    // the default keyword below lets me override this method impl in more specific impls elsewhere
    default fn encode_link(self, link: &Link) -> Result<Self::Ok> {
        self.serialize_u8(link.0)
    }
}

In crate B:

#![feature(specialization)]
use crateA::{Encoder, Link};

struct MyJsonEncoder(serde_json::Serializer);

// &'a mut b/c of `serde_json::Serializer`, otherwise the lifetime changes nothing
impl<'a> Serializer for &'a mut MyJsonEncoder {
    // ... delegates all `Serializer` methods to the wrapped `serde_json::Serializer`
}

impl<'a> Encoder for &'a mut MyJsonEncoder {
    // ... serialization behaviour for `Link`s specific to this crate's `Encoder`/`Serializer`
    // ... allowed b/c of #[feature(specialization)] and the `default` keyword on the blanket method impl`
    fn encode_link(self, link: &Link) -> Result<S::Ok> {
        self.serialize_none()
    }
}

fn main() {
    let link = Link(1);
    let mut writer = Vec::new();
    let mut ser = Encoder(serde_json::Serializer::new(writer));
    link.serialize(&mut ser)?;

    let string = String::from_utf8(writer).unwrap();
    println!("result: {}", string) // prints "1", expected "null"
}

In essence, the issue is that crateA::Link::serialize only ever calls the default crateA::Encoder::encode_link, despite me passing in a crateB::MyJsonEncoder which overrides the default impl.

How do I get Link::serialize to call any given Serializer's encode_link method impl, or the default if not overridden?

UPDATE: I fixed it, and the bug was due to not recursing with my Encoder but instead recursing with the original Serializer.

This is a shot in the dark but the following cast looks wrong to me:

    impl Serialize for Link {
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok>
        where
            S: serde::Serializer,
        {
            <S as Encoder>::encode_link(serializer, self) // <-- S is now an Encoder !!!
        }
    }

Would something like this work?

    impl Serialize for Link {
        fn serialize<S>(&self,  serializer: S) -> Result<S::Ok>
        where
            S: serde::Serializer + Encoder,  // S is an encoder
        {
            // s is still the same type
            s.encode_link(self)
        }
    }

I am not sure if this is still a valid impl of serialize() because there is an extra type constraint.

where
S: serde::Serializer + Encoder, // S is an encoder
{
// s is still the same type
s.encode_link(self)
}

The nightly compiler doesn't complain about the extra type constraint, but it still doesn't work.

Interestingly enough, I added another method encode_bytes and its default impl to the Encoder trait, and when calling it within a different type's serialize, the overridden version is called.

I asked this question elsewhere and this is a repro of a comment I left there providing real code to run:

Here's the link to the repo + branch + folder. cd rust and cargo test should get you started.

  • /rust/ipld_dag/src/dag/mod.rs has a type Dag that requires an Encoder
  • /rust/ipld_dag/src/cid.rs has the example Link type (called CID )
  • /rust/ipld_dag/src/format.rs defines Encoder
  • /rust/ipld_dag_json/src/lib.rs has an impl of Encoder and the tests

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.