3

I have this code:

extern crate serde;

use serde::de::DeserializeOwned;
use serde::Serialize;

trait Bar<'a, T: 'a>
where
    T: Serialize,
    &'a T: DeserializeOwned,
{
}

I would like to write this using an associated type, because the type T is unimportant to the users of this type. I got this far:

trait Bar {
    type T: Serialize;
}

I cannot figure out how to specify the other bound.

Ultimately, I want to use a function like this:

extern crate serde_json;

fn test<I: Bar>(t: I::T) -> String {
    serde_json::to_string(&t).unwrap()
}
dtolnay
  • 9,621
  • 5
  • 41
  • 62
corvus_192
  • 362
  • 4
  • 15

1 Answers1

4

The "correct" solution is to place the bounds on the trait, but referencing the associated type. In this case, you can also use higher ranked trait bounds to handle the reference:

trait Bar
where
    Self::T: Serialize,
//  ^^^^^^^ Bounds on an associated type  
    for<'a> &'a Self::T: DeserializeOwned,
//  ^^^^^^^^^^^ Higher-ranked trait bounds       
{
    type T;
}

However, this doesn't work yet.

I believe that you will need to either:

In the meantime, the workaround is to duplicate the bound everywhere it's needed:

fn test<I: Bar>(t: I::T) -> String
where
    for<'a> &'a I::T: DeserializeOwned,
{
    serde_json::to_string(&t).unwrap()
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Thank you, I didn't know you could `Self` in that case. But it doesn't seem to work as I expected. I have this function: `fn test(t: I::T) -> String { serde_json::to_string(&t).unwrap() }`. Compilation fails with "the trait `for<'a> DeserializeOwned` is not implemented for `&'a ::T`". I think that by requiring that t is a I::T, there must be a DeserializeOwned for &'a t forall 'a. Am I misunderstanding something? – corvus_192 Apr 29 '18 at 20:45
  • @corvus_192 I think that's because there's no `I` in the signature of `test` for the compiler to see. You could implement multiple types `I` for which `::T` was the same thing, right? – trent Apr 29 '18 at 20:59
  • @trentcl In my case there's only one implementation of Bar, but there could obviously be more than one. Why does it matter, as long as every implementation provides a type T that satisfies the given bounds? – corvus_192 Apr 29 '18 at 21:13
  • @corvus_192 The compiler is not very good at proving universal propositions. A signature like `fn test(t: I::T) -> String` implies that you could pick *any* `I` that implements `Bar`, and the compiler has to prove that there is a `DeserializeOwned` impl for *every* `I` where `::T` is whatever the type of `t` actually is. I'm doing a poor job of explaining. Point is, it's logically sound, but the compiler can't *prove* that there isn't some `I` for which `&::T` *doesn't* implement `DeserializeOwned`. – trent Apr 29 '18 at 23:22
  • 1
    The upshot of all this is that you have to duplicate the bound `for<'a> &'a I::T: DeserializeOwned` on `test` because just having it on `Bar` is not sufficient. [example](https://play.rust-lang.org/?gist=f57f3c86590f5b1950f20f6f2d8e4235&version=stable&mode=debug) – trent Apr 29 '18 at 23:54