4

I would like to implement Borrow for UserFriendlyDataStructure to provide access to the internal_data field within a function that should be agnostic with respect to the data provider. The type of the internal_data field is determined by the type associated to trait TraitA. Note that the Sealed trait ensures that none of these traits here can be implemented by other crates; this is functionality that strictly I provide. Furthermore, the type TraitA::Data is restricted by the empty trait DataTrait to prevent UserFriendlyDataStructure from being used as that type.

The following example explains best:

use std::borrow::Borrow;
use std::marker::PhantomData;

mod private {
    pub trait Sealed {}
}

pub trait DataTrait: private::Sealed {}

pub trait TraitA: private::Sealed {
    type Data: DataTrait;
}

pub struct UserFriendlyDataStructure<A: TraitA> {
    internal_data: A::Data,
    _a: PhantomData<A>,
}

impl<A: TraitA> Borrow<A::Data> for UserFriendlyDataStructure<A> {
    fn borrow(&self) -> &A::Data {
        &self.internal_data
    }
}

pub fn important_function<A: TraitA, T: Borrow<A::Data>>(data: &T) {
    let _internal_data = data.borrow();
    // Do lots of work.
}

#[cfg(test)]
mod tests {
    use super::*;

    pub struct TestData(u32);

    impl super::private::Sealed for TestData {}

    impl DataTrait for TestData {}

    pub struct TestProvider;

    impl super::private::Sealed for TestProvider {}

    impl TraitA for TestProvider {
        type Data = TestData;
    }

    #[test]
    fn basic_test() {
        let ufds: UserFriendlyDataStructure<TestProvider> = UserFriendlyDataStructure {
            internal_data: TestData(100),
            _a: PhantomData::default(),
        };

        important_function::<TestProvider, _>(&ufds);
    }
}

Unfortunately, the compiler complains:

error[E0119]: conflicting implementations of trait `std::borrow::Borrow<UserFriendlyDataStructure<_>>` for type `UserFriendlyDataStructure<_>`:
  --> src/lib.rs:19:1
   |
19 | impl<A: TraitA> Borrow<A::Data> for UserFriendlyDataStructure<A> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: conflicting implementation in crate `core`:
           - impl<T> std::borrow::Borrow<T> for T
             where T: ?Sized;

Is there a way to achieve what I am trying to do?

E Y
  • 281
  • 2
  • 11
  • 1
    Is this the same as https://github.com/rust-lang/rust/issues/32315? – phimuemue Oct 27 '18 at 14:56
  • It does look like it's the same issue. I'm not too happy that it would require the use of specialization to solve though, since that's not stable yet. The alternatives I see is to write the implementations out by hand for all types that implement `TraitA` (which is not very flexible), or to use `AsRef` instead of `Borrow` (which is suboptimal for other reasons in my case). – E Y Oct 27 '18 at 15:54
  • 1
    Is there any reasonable trait bound you could apply to `Data`? If you can restrict it so it can't be `UserFriendlyDataStructure`, the error should be resolved. – Sven Marnach Oct 27 '18 at 16:39
  • `Data` really allows arbitrary types. Since I control which types implement `TypeA` (it's an internal trait that should never have external implementations), I can create an artificial trait bound to `Data` (e.g. an empty trait). I will try that and write an answer if it works. – E Y Oct 27 '18 at 17:03
  • Unfortunately, adding a trait bound to `Data` (via `TraitA`) didn't work. I can update the original question or add an answer with my attempt at solving it that way. What should I do? – E Y Oct 27 '18 at 17:36
  • Hm, then there seems to be another bug here. Since it didn't work, updating the question seems more appropriate. – Sven Marnach Oct 27 '18 at 17:44

1 Answers1

3

The compiler can be cajoled into accepting the code by introducing a redundant second type parameter that is constrained to be identical to A::Data:

impl<A, D> Borrow<D> for UserFriendlyDataStructure<A>
where
    A: TraitA<Data = D>,
    D: DataTrait,
{
    fn borrow(&self) -> &A::Data {
        &self.internal_data
    }
}

I don't know why this works, and simply constraining A::Data: DataTrait doesn't. I think the compiler should accept both versions.

(Full code on the playground)

Edit: The fact that we need the redundant type D in the above code appears to be a shortcoming of the current compiler implementation, and is hopefully resolved once the experimental type inference engine chalk gets integrated in the compiler.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • That worked, thank you! Do you think I should open an issue on the `rust` repository? – E Y Oct 27 '18 at 19:13
  • @Eleanore Probably. Make sure to add a link to the issue phimuemue mentioned above. – Sven Marnach Oct 27 '18 at 19:16
  • 1
    *I don't know why this works* — because generics are considered "input" types and associated types are considered "output" types. ([When is it appropriate to use an associated type versus a generic type?](https://stackoverflow.com/q/32059370/155423)). Multiple trait implementations can have the same output type so it's ambiguous. Placing it as an input type requires it to be unambiguous (because of coherence). – Shepmaster Oct 28 '18 at 21:00
  • @Shepmaster I still don't really understand this. If I only have a single type parameter `A: TraitA` and I add the bound `A::Data: DataTrait`, it is guaranteed that `A::Data` can't be `UserFriendlyDataStructure`, so there are no conflicting implementations. Why is the compiler complaining in this case? (I'm setting aside the fact that `A::Data` can't ever be `UserFriendlyDataStructure` anyway, so there really isn't a conflict here in any case, but I can understand that the compiler can't see that.) – Sven Marnach Oct 28 '18 at 21:11
  • 1
    *it is guaranteed that `A::Data` can't be `UserFriendlyDataStructure`* — why not? Nothing prevents someone from implementing that. This question is basically a duplicate of [How is there a conflicting implementation of `From` when using a generic type?](https://stackoverflow.com/q/37347311/155423). – Shepmaster Oct 28 '18 at 21:17
  • @Shepmaster The version with two type parameters only works when adding the additional `DataTrait` constraint, so it's clearly that constraint that makes the compiler think `D` can't be `UserFriendlyDataStructure`. If `UserFriendlyDataStructure` implemented the trait `DataTrait`, the compiler would now, since it would have to be in the current crate. If this constraint helps the compiler understand that `A::Data` can't be `UserFriendlyDataStructure` in one version, why doesn't the compiler understand this in the other version? – Sven Marnach Oct 28 '18 at 21:22
  • 1
    @Shepmaster If you are still interested, I've boiled the point I don't understand down to a minimal example: [version that compiles](https://play.rust-lang.org/?version=stable&mode=debug&edition=2015&gist=767c2ec35433903f08bf1ef63770ab57), [version that doesn't](https://play.rust-lang.org/?version=stable&mode=debug&edition=2015&gist=2ee279e043b119e49122f9fb239748d6). I've thoroughly read all the threads you linked, but I found no explanation for this behaviour, and I still think that the two versions should be equivalent. – Sven Marnach Oct 29 '18 at 10:57
  • 1
    @EleanoreY No need to report an issue for this – there already is one: https://github.com/rust-lang/rust/issues/50237 – Sven Marnach Oct 29 '18 at 16:48