1

I am trying to write a generic method that accepts a function that returns either a Serialize value or an Arc<Serialize> value. My solution is to create a trait to unwrap the Arc if needed and produce a reference to the underlying value:

use serde::Serialize;
use std::sync::Arc;

pub trait Unwrapper {
    type Inner: Serialize;

    fn unwrap(&self) -> &Self::Inner;
}

impl<T> Unwrapper for T
where
    T: Serialize,
{
    type Inner = T;
    fn unwrap(&self) -> &Self::Inner {
        self
    }
}

impl<T> Unwrapper for Arc<T>
where
    T: Serialize,
{
    type Inner = T;
    fn unwrap(&self) -> &Self::Inner {
        self
    }
}

fn use_processor<F, O>(processor: F)
where
    O: Unwrapper,
    F: Fn() -> O,
{
    // do something useful processor
}

I get a E0119 error due to the potential that Arc may implement Serialize in the future, like if I enable the serde crate's feature to allow just that:

error[E0119]: conflicting implementations of trait `Unwrapper` for type `std::sync::Arc<_>`:
  --> src/lib.rs:20:1
   |
10 | / impl<T> Unwrapper for T
11 | | where
12 | |     T: Serialize,
13 | | {
...  |
17 | |     }
18 | | }
   | |_- first implementation here
19 | 
20 | / impl<T> Unwrapper for Arc<T>
21 | | where
22 | |     T: Serialize,
23 | | {
...  |
27 | |     }
28 | | }
   | |_^ conflicting implementation for `std::sync::Arc<_>`

I don't want to do this as I only want to allow the Arc at the top level and not within the value (for the same reasons the feature is not on by default). Given this, Is there a way to disable my first impl only for an Arc? Or is there a better approach to the to problem?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
nate
  • 1,771
  • 12
  • 17
  • Does this answer your question? [How do I implement a trait I don't own for a type I don't own?](https://stackoverflow.com/questions/25413201/how-do-i-implement-a-trait-i-dont-own-for-a-type-i-dont-own) – Stargateur Feb 13 '20 at 21:03
  • The problem is I cannot create "more specialized" impl (the second one) for a trait that supersedes the more generic one (the first one), so that I can provide special handling for this one subset of types (Arc) – nate Feb 13 '20 at 21:09
  • 2
    Maybe look at specialisation https://github.com/rust-lang/rfcs/blob/master/text/1210-impl-specialization.md – Stargateur Feb 13 '20 at 21:10
  • Thanks, I think that is what I was looking for with this piece of code, but I guess it is still only available on nightly. – nate Feb 13 '20 at 21:17
  • 2
    You might be able to use a trick like [autoref-based stable specialization](https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md) or [deferring trait method resolution to the call site using generics](https://stackoverflow.com/a/52692592/3650362). [I tried the second technique](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=ee85f5c5d98fc68d710433c159271e1f), but I don't know if it worked because [the playground uses Serde with the `rc` feature](https://github.com/integer32llc/rust-playground/blob/master/compiler/base/Cargo.toml#L838). – trent Feb 13 '20 at 21:58

1 Answers1

3

Your attempt does not work because it is not possible to have overlapping implementations of a trait.

Below an attempt to write a generic method that accept a Serialize value or an Arc of a Serialize value.

It leverages the Borrow trait and its blanket implementation for any T.

Note the use of the turbo fish syntax on the calling site of the generic method.

use std::sync::Arc;
use std::borrow::Borrow;
use serde::Serialize;

#[derive(Serialize, Debug)]
struct Point {
    x: i32,
    y: i32,
}

fn myserialize<T: Borrow<I>, I: Serialize>(value: T) {
    let value = value.borrow();
    let serialized = serde_json::to_string(value).unwrap();
    println!("serialized = {}", serialized);
}


fn main() {
    let point = Point { x: 1, y: 2 };
    myserialize(point);

    let arc_point = Arc::new(Point { x: 1, y: 2 });
    myserialize::<_, Point>(arc_point);

}
attdona
  • 17,196
  • 7
  • 49
  • 60
  • Thanks, I realize I missed an aspect of the problem. Is there a way to wrap up `T: Borrow, I: Serialize` to describe the return value of a function like `Fn() -> ???`, I will update my question to include that addition detail. – nate Feb 14 '20 at 15:27
  • 1
    Try the signature `fn use_processor(processor: F) where O: Borrow, I: Serialize, F: Fn() -> O {}` – attdona Feb 14 '20 at 15:49
  • Thanks, that worked. The requirement to use the turbo fish is annoying since I have 6 type parameters (so a bunch of extra `_`s in the call), but i can live with that. Am I correct if believing that if specialization lands my original approach would work? – nate Feb 14 '20 at 17:54
  • I've tried the latest version of your code with specialization feature but I was not able to get it working: see [here](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=f1f035b959c513016e86ca3aa8ba9112). – attdona Feb 14 '20 at 19:53
  • I would expect that this would work as it seems obvious to me that `T` and `Inner` are the same type (i.e. some type that is `Serialize`). Maybe this is a limitation of the current implementation of specialization. – nate Feb 14 '20 at 19:59
  • Thanks to [the answers](https://users.rust-lang.org/t/behavior-of-specialization-with-associated-types/38227) from the rust forum the conclusion is that specialization is not suitable for your original approach. – attdona Feb 17 '20 at 10:51