Trait bound on generic serde serialize wrapper

I can't seem to get the right trait bound on a generic wrapper struct for serde serialize.

I wanted to write a wrapper struct which would just call serialize map (without having to put things in a btreemap or hashmap). I accomplished this, but I'm not able to use my wrapper struct on all the types I would like.

Any ideas for how to change the bound on the impl block so that the struct can be used as seen in using_newtype_shared and using_newtype_owned.

Playground Example

//
// [package]
// name = "serialize-benchmark"
// version = "0.1.0"
// edition = "2021"
//
// [dependencies]
// serde = { version = "1.0.204", features = ["derive"] }
// serde_json = "1.0.120"
//
// [dev-dependencies]
// divan = "0.1.14"
//
// [[bench]]
// name = "bench"
// harness = false
//
//
// in benches/bench.rs
use divan::main;

struct SerializeAsMap<I>(I);
impl<I, K, V> serde::Serialize for SerializeAsMap<I>
where
    I: IntoIterator<Item = (K, V)> + Copy, // not a fan of having to have the copy bound here
    K: serde::Serialize,
    V: serde::Serialize,
{
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.collect_map(self.0)
    }
}

#[divan::bench]
fn using_newtype_owned() -> String {
    let a = std::hint::black_box("asdf");
    let x = [("a", a)];
    let y = SerializeAsMap(x);
    serde_json::to_string(&y).unwrap()
}

#[divan::bench]
fn using_newtype_shared() -> String {
    let a = std::hint::black_box("asdf");
    let x = [("a", a)];
    let y = SerializeAsMap(&x);
    serde_json::to_string(&y).unwrap() // this doesn't compile
}

#[divan::bench]
fn using_serde_json() -> String {
    let a = std::hint::black_box("asdf");
    let x = serde_json::json!({"a": a});
    serde_json::to_string(&x).unwrap()
}

#[divan::bench]
fn using_hashmap() -> String {
    let a = std::hint::black_box("asdf");
    let x = std::collections::HashMap::from([("a", a)]);
    serde_json::to_string(&x).unwrap()
}
$ cargo bench
     Running benches/bench.rs (target/release/deps/bench-ab596dc39b16c93b)
Timer precision: 15 ns
bench                   fastest       โ”‚ slowest       โ”‚ median        โ”‚ mean          โ”‚ samples โ”‚ iters
โ”œโ”€ using_hashmap        69.65 ns      โ”‚ 664.3 ns      โ”‚ 71.82 ns      โ”‚ 106.6 ns      โ”‚ 100     โ”‚ 3200
โ”œโ”€ using_newtype_owned  34.86 ns      โ”‚ 3.905 ยตs      โ”‚ 43.54 ns      โ”‚ 96.31 ns      โ”‚ 100     โ”‚ 3200
โ•ฐโ”€ using_serde_json     129.7 ns      โ”‚ 1.55 ยตs       โ”‚ 138.7 ns      โ”‚ 156.1 ns      โ”‚ 100     โ”‚ 100

Your playground compiles and runs. What's the problem?

see lines 30-35 in the playground

// Uncomment for compilation error
// serde_json::to_string(&y).unwrap()
// can't seem to figure out a way to define a trait bound
// to work for for both arrays and slices
todo!()

Ha, I see!

I don't think you can make it work as-is, since iterators always produce a single type, so you can't say "an iterator that produces a tuple or a reference of tuples".

You can however write a custom trait, by which you smuggle pairs and references-to-pairs through the type system, treating them in the same manner.

Thanks, if supporting both pairs and reference to pairs requires defining a trait, instead I might just support reference to pair. I see you implemented the serialization using serialize_map and serialize_entry, I'm trying to figure out how I could make use of serializer.collect_map

However when I start trying to modify from this starting point I quickly run into lifetime issues. Right now I've gotten this incorrect hideous mess with the help of the compiler.

Why is that important? You aren't going to get references to pairs if you don't specify that the iterator item is itself a reference to begin with โ€“ because you can't return a reference to a local from the projection function.

In this case: solution 1, solution 2.

1 Like

Why is that important?
Just learning, thanks for the answer. That's what I reacted for first and then when I struggled with figuring out the lifetime part I became more interested in that part.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.