I'm struggling to understand a "downstream crates may implement trait" error that reduces to the following example (playground):
/// Some trait dependent on a tuple of arguments.
pub trait Foo<Args> {}
/// Blanket implementation for async closures.
impl<F, Args> Foo<Args> for F
where
Args: Tuple,
F: AsyncFnOnce<Args>,
{
}
/// We also define our own private struct that implements the trait.
struct Bar {}
impl<Args> Foo<Args> for Bar where Args: Tuple {}
error[E0119]: conflicting implementations of trait `Foo<_>` for type `Bar`
--> src/lib.rs:22:1
|
11 | / impl<F, Args> Foo<Args> for F
12 | | where
13 | | Args: Tuple,
14 | | F: AsyncFnOnce<Args>,
| |_________________________- first implementation here
...
22 | impl<Args> Foo<Args> for Bar where Args: Tuple {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Bar`
|
= note: downstream crates may implement trait `std::ops::AsyncFnOnce<_>` for type `Bar`
It's not true that downstream crates can implement AsyncFnOnce for Bar, right? They can't mention that type, which is private, and even if they could it is not a local type for them so they shouldn't be able to implement AsyncFnOnce for it.
How do I make this work? I feel like I should be able to use sealed traits, even though that seems like it should be unnecessary, but I can't find an arrangement that works.
Coherence doesn't take privacy or something being "sealed" into account, to date.
You may have read "you can't implement foreign traits for foreign types" somewhere, but the actual rules are more nuanced. You can implement AsRef<LocalTy> for Vec<()> for example.
As it turns out, if you ignore the the privacy of Bar, it's still not possible for downstream to implement AsyncFnOnce<LocalTy> for Bar -- but only because they'd have to implement Tuple for Bar, and Tuple is a magic trait which only the compiler can implement.
However, coherence doesn't take the magic of Tuple into account either.
I suppose this doesn't meet your needs?
impl<Args> AsyncFnOnce<Args> for Bar where Args: Tuple {
Unfortunately it doesn't, because I'm doing this in the first place to work around this issue where HRTBs cause lifetimes to be required to be 'static. In reality Foo is a trait saying "my future is Send for any lifetime", and my blanket implementation uses an HRTB to opt in all AsyncFnMuts where that is true according to the compiler. But I want users to be able to opt in (through Bar) to declaring that is true for closures that capture references, with an unsafe API that wraps a closure and implements Foo without an HRTB involved (and maybe a macro that does this safely by checking one lifetime, which I think is sound).
(Edit: See here for more on what I'm trying to do.)