1

I can't get the following code to work (playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=4379c2006dcf3d32f59b0e44626ca667).

use serde::{Serialize, Deserialize};

trait InnerStruct<'delife>: Deserialize<'delife> + Serialize {}

#[derive(Serialize, Deserialize)]
struct InnerStructA{
    a: i32
}

impl InnerStruct<'_> for InnerStructA {}

#[derive(Serialize, Deserialize)]
struct InnerStructB{
    a: i32,
    b: i32
}

impl InnerStruct<'_> for InnerStructB {}

#[derive(Serialize, Deserialize)]
struct OuterStruct<T: InnerStruct>{   // Remove the word "InnerStruct" and this works
    c: f64,
   inner: T
}

fn print_json<T: for<'a> InnerStruct<'a>>(obj: T) {
    println!("Serde JSON: {:?}", serde_json::to_string(&obj).unwrap());
}

fn main() {
    let inner_a = InnerStructA{a: 123};
    let inner_b = InnerStructB{a: 234, b: 567};

    println!("Serde JSON: {:?}", serde_json::to_string(&inner_a).unwrap());
    println!("Serde JSON: {:?}", serde_json::to_string(&inner_b).unwrap());
    
    print_json(inner_a);
    print_json(inner_b);
}

I have a collection of structs that are serializable (InnerStructA, InnerStructA), and they all implement a trait. Some functions are generic across a trait that unified them (InnerStruct). Some of these functions require that they are serializable and deserializable, so I've added Deserialize and Serialize supertraits to the trait definition. Deserialize required adding a named lifetime.

I now want an OuterStruct that is a generic container that could hold any type of inner struct. It works fine if I don't apply any trait bounds, but when I try to apply a trait bound to say this struct is only valid for T being InnerStruct everything breaks. The compiler messages talk about lifetimes, but none of the suggestions work.

A concrete example: For

struct OuterStruct<T: InnerStruct> {

the compiler suggests to

help: consider introducing a named lifetime parameter
   |
23 | struct OuterStruct<'a, T: InnerStruct<'a>> {

but doing so leads to another error

error[E0392]: parameter `'a` is never used
  --> src/main.rs:23:20
   |
23 | struct OuterStruct<'a, T: InnerStruct<'a>> {
   |                    ^^ unused parameter

What am I doing wrong?

Edit: DeserializeOwned If the trait is changed to DeserializedOwned then the lifetime issues go away, but the problem remains. It appears to be something to do with applying the derive(Deserialize) to the OuterStruct which already contains something that has had derive(Deserialize) applied to it. The error message is:

note: multiple `impl`s or `where` clauses satisfying `T: Deserialize<'_>` found
Caesar
  • 6,733
  • 4
  • 38
  • 44
Corvus
  • 7,548
  • 9
  • 42
  • 68
  • 2
    Maybe you want `DeserializeOwned` instead? – drewtato May 09 '23 at 22:39
  • 1
    @drewtato that doesn't fix things, but gives a bit of a clearer insight - it seems the problem is something to do with the derive (I'll edit the question) – Corvus May 09 '23 at 22:49
  • Okay well, that's about as much help as we can give you without seeing any broken code. The playground link works fine. It sounds like it's related to this common issue: https://stackoverflow.com/q/49229332/6274355 – drewtato May 09 '23 at 22:55
  • 1
    @drewtato The broken code is in the post. Note that it differs slightly from the linked playground. (I've taken the liberty of editing the post to add a few of the errors.) – Caesar May 10 '23 at 00:10

1 Answers1

5

It's usually a good idea not to put any unnecessary bounds on a struct or enum. It's more flexible that way, especially when dealing with traits that have lifetime parameters.

So I would try something like this:

#[derive(Serialize, Deserialize)]
struct OuterStruct<T> {
    c: f64,
    inner: T,
}

fn print_json<'a, T>(obj: T)
where
    T: InnerStruct<'a>,
{
    println!("Serde JSON: {:?}", serde_json::to_string(&obj).unwrap());
}

playground link


In your example program, this will also work:

trait InnerStruct: DeserializeOwned + Serialize {}

...

#[derive(Serialize, Deserialize)]
struct OuterStruct<T: InnerStruct> {
    c: f64,
    #[serde(bound(deserialize = ""))]
    inner: T,
}

playground link

Your original code wasn't working because you had a bound on the struct; and the #[derive(Deserialize)] macro was copying that bound onto its impl<'a> Deserialize<'a> for the struct, but also adding a T: Deserialize<'a> bound. Usually that T: bound is necessary, but in this case I guess Rust didn't like seeing T: Deserialize required in two different ways. So the solution was to tell the macro not to emit its usual bound on T.

The serde(bound) attribute is documented here.

Jason Orendorff
  • 42,793
  • 6
  • 62
  • 96
  • Fantastic! Thank you. In this case I do need the bound on the struct, otherwise generic functions that work on that struct won't pick up the methods from the trait bound. I possibly should have added such a function that took OuterStruct as a parameter in the example, but the example was already quite long. – Corvus May 10 '23 at 08:08
  • Actually - I've had a play with your first solution, and I now understand what you mean - so you put the bounds on the function not the struct. I've managed to take your example and make it work for my extended idea: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=be68f2612e3023507211c461077076b7 – Corvus May 10 '23 at 08:17
  • @Corvus Right. An example from libstd is [Reverse](https://doc.rust-lang.org/std/cmp/struct.Reverse.html). It doesn't require `T: Ord` or `T: PartialOrd`; instead the bounds are on the the impls, `impl Ord for Reverse where T: Ord`. This is more flexible: `Reverse` can do its thing for types like `f64` that only implement PartialOrd and not Ord, for example. – Jason Orendorff May 10 '23 at 15:56