Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upCoherence and blanket impls interact in a suboptimal fashion with generic impls #19032
Comments
nikomatsakis
changed the title
Cross-crate coherence and blanket impls interact in a suboptimal fashion
Coherence and blanket impls interact in a suboptimal fashion with generic impls
Nov 18, 2014
This comment has been minimized.
This comment has been minimized.
|
Updated title to reflect @arielb1's point that this problem is not specific to cross-crate. Really, it has to do with the fact that current coherence rules are designed so that, when implementing |
This comment has been minimized.
This comment has been minimized.
|
Actually, that's not strictly true. This is somewhat specific to cross-crate, because within a crate, we can search through the existing impls, but in a cross-crate scenario, we want to avoid having to retest all of the downstream impls in light of the impls added in this new crate. |
This comment has been minimized.
This comment has been minimized.
|
I think |
This comment has been minimized.
This comment has been minimized.
|
I actually really like that |
This comment has been minimized.
This comment has been minimized.
|
Why not move directly to unrestricted overloading then? Top-level |
This comment has been minimized.
This comment has been minimized.
This is a pretty key point to me: while I recognize the power/utility of full-blown overloading, it would be very strange to have it only for closures. But I also want to stress that the status of the types is explicitly not going to be nailed down at 1.0, and we should take our time deciding what kinds of additional overloading, if any, we want throughout Rust. (@reem, it's worth keeping in mind that there are tradeoffs lurking here with type inference among other things, so it's not a straightforward question of "just" giving you extra power.) |
This comment has been minimized.
This comment has been minimized.
|
Well the thing is you already get full overloading since there isn't really anything special about the Fn family. It's just a matter of making it easy or making it require some perverse macro. |
This comment has been minimized.
This comment has been minimized.
If you count what you can do with macros or weird encodings, the language already has a lot more features than advertised :-) More seriously, there is something special about Again, I'm not saying this kind of overloading is bad or undesirable, just that the language design should be coherent. |
This comment has been minimized.
This comment has been minimized.
|
I agree with your concerns, but still lean on the side of enabling overloading. I think the most complex thing to deal with if we do enable overloading will be dealing with conflicting blanket implementations, for instance if I write this impl: impl<F: Fn(X) -> Y> SomeTrait for F { ... }I cannot also write this impl: impl<F: Fn(X, Y) -> Z> SomeTrait for F { ... }even though in practice they almost never conflict. As a result, we'd need negative trait bounds, and we'd need to put a lot of them ( |
japaric
referenced this issue
Dec 10, 2014
Closed
"error: reached the recursion limit during monomorphization" with unboxed closures #19596
This comment has been minimized.
This comment has been minimized.
|
I've been giving this a lot of thought. I no longer believe that the args/ret-types of the fn traits should be associated types. The reason is that having them as associated types is a problem in higher-ranked situations, since in that case the lifetime must be specified by the caller. Consider an example like
Here, the parameter Note that we could make
But now, for any given instance of I'm busy right now so I'll stop this here. Obviously we'll have to address this issue, I have some thoughts on how to do it but I want to work them out a bit (short version is to have coherence be more liberal). |
This comment has been minimized.
This comment has been minimized.
By this do you mean |
arielb1
referenced this issue
Jan 4, 2015
Closed
Remove the remaining uses of `old_orphan_rules` #19470
This comment has been minimized.
This comment has been minimized.
@reem I believe you could emulate this pattern by having a phantom parameter to the struct (just abstract over it when you don't care). You'd retain the flexibility while having the better type inference characteristics of associated types. |
This comment has been minimized.
This comment has been minimized.
|
Glad I found this issue...seems that problem still exists. #![feature(unboxed_closures)]
struct Foo<Ret> (Ret);
impl<T> FnMut()->T for Foo<T> {
extern "rust-call" fn call_mut(&mut self, _: ())->T { self.0 }
}
fn main() {}
|
This comment has been minimized.
This comment has been minimized.
|
How about fix this coherence problem by unifying Fn/FnOnce/FnMut using associated types? struct Counter(u64);
impl Counter { fn new()->Counter { Counter(0) } }
impl Callbale<(), u64> for Counter {
type SelfType = &mut Counter;
fn call(this: &mut Counter, _: ())->u64{
this.0 = this.0 + 1;
this.0
}
}That said, we use static method of Fn/FnOnce/FnMut to implement them, passing The downside of this idea is that now you can only choose to impl one of them for one type when parameter type is the same. |
nikomatsakis
referenced this issue
Jan 20, 2015
Merged
Make return type of the `Fn` traits an associated type #587
steveklabnik
added
the
A-typesystem
label
Jan 29, 2015
nikomatsakis
referenced this issue
Mar 11, 2015
Merged
Change the `Fn` traits to use inheritance, not bridge impls #23282
nikomatsakis
referenced this issue
Mar 19, 2015
Closed
overlap check fails to prevent overlap #23516
bors
added a commit
that referenced
this issue
Mar 24, 2015
alexcrichton
added a commit
to alexcrichton/rust
that referenced
this issue
Mar 24, 2015
nikomatsakis
referenced this issue
Feb 2, 2016
Closed
Can't provide non-overlapping impls with *any* type parameters, if a blanket impl exists #30191
This comment has been minimized.
This comment has been minimized.
|
Triage: still seems relevant, even though a lot of details have changed since the original discussion was happening, as those were more details about the Fn traits than the underlying type system issue. I agree with @nikomatsakis that this feels like an older #30191 (comment) |
jethrogb
referenced this issue
Jul 16, 2016
Closed
`Fn`s with different arguments should be different types #34868
This comment has been minimized.
This comment has been minimized.
|
I closed #30191 as a dup of this issue. I wanted to reproduce a lightly edited version of the salient comments for posterity: @arielb1 wrote this example, which was intended to demonstrate where the overlap rules were being too strict: struct Row;
pub trait FromSql<A> {}
pub trait FromSqlRow<A> {
fn foo(&self);
}
impl<ST, T> FromSqlRow<ST> for T where
T: FromSql<ST>,
{
fn foo(&self) {}
}
struct User;
impl<ST> FromSqlRow<ST> for User where
(i32, String): FromSqlRow<ST>
{
fn foo(&self) {}
}
fn main() {}but as he and I later concluded, the orphan check is doing the right thing here. The danger is that a downstream crate might do something like:
which they are legally able to do, for better or worse. One solution here would be to adopt one of the negative impl proposals (such as what I describe in this gist]) that would permit |
aturon
referenced this issue
Aug 27, 2016
Open
Tracking issue for Fn traits (`unboxed_closures` feature) #29625
This comment has been minimized.
This comment has been minimized.
Maybe it's the downstream crate that should be blamed by the coherence checker? At that point both the blanket and the generic impls should be visible. |
This comment has been minimized.
This comment has been minimized.
|
Any update on this? Basic pattern: trait A<P> {}
trait B<P> {}
impl<P> B<P> for i32 {}
impl<P, T: A<P>> B<P> for T {}Seems like it could be resolved by just banning simultaneous impls of |
This comment has been minimized.
This comment has been minimized.
|
No update, other than the fact that we've been slowly making progress towards a more canonical way of representing coherence and the Rust trait system, and it may help us feel comfortable with selecting a solution.
Potentially yes but this is not such a minor thing. Among other things, it implies a much more complex coherence check, since now an impl of |
This comment has been minimized.
This comment has been minimized.
|
@nikomatsakis Why is this so different from the non-parametric versions of the traits (which are allowed)? This compiles:
W.R.T. the original example: you said "an impl of |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
Edit: Disregard what this comment used to say. So if I understand correctly, it's just that this particular instance of negative reasoning was allowed, but the relevant RFC didn't allow negative reasoning to be extended to traits that are parametric in this way. |
This comment has been minimized.
This comment has been minimized.
|
@cramertj one might view it that way. The difference is that in the simpler, non-generic case, the local crate can verify that no impls of In the generic case, we can validate that no impls exist that the crate can see, but there may be other impls added in downstream crates. This implies that downstream crates would have to do their own checking as well on our behalf, and this is what we wanted to avoid. |
This comment has been minimized.
This comment has been minimized.
|
@nikomatsakis That makes sense. Thank you for the explanation. |
aidanhs
referenced this issue
Mar 23, 2017
Open
impl Fn with generic associated type conflicts, succeeds with concrete type #40761
nikomatsakis
referenced this issue
Jun 14, 2017
Closed
Allow `default type` to be projected in the fully monomorphic case #42411
Mark-Simulacrum
added
C-enhancement
T-lang
labels
Jul 22, 2017
This comment has been minimized.
This comment has been minimized.
|
Ran into the exact case in #19032 (comment) . Got some unparameterised traits which rely on blanket impls like that but would like to generalize them to take a parameter which is not possible as it is currently :(. |
nikomatsakis commentedNov 17, 2014
There is a tricky scenario with coherence rules and blanket impls of traits. This manifested as #18835, which is an inability to implement
FnOncemanually for generic types. It turns out to be a legitimate coherence violation, at least according to the current rules (in other words, the problem is not specific toFnOnce, but rather an undesired interaction between blanket impls (like those used in the fn trait inheritance hierarchy) and coherence. I am not sure of the best fix, though negative where clauses would provide one possible solution. Changing theFntype parameters to associated types, which arguably they ought to be, would also solve the problem.Let me explain what happens. There is a blanket impl of
FnOncefor all things that implementFnMut:Now imagine someone tries to implement
FnOncein a generic way:If you try this, you wind up with a coherence violation. The coherence checker is concerned because it is possible that someone from another crate comes along implements
FnMutforThunkas well:This impl passes the orphan check because
SomeSpecificTypeis local to the current crate. Now there is a coherence problem with respect toFnOnceforThunk<SomeSpecificType>-- do use the impl that delegates toFnMut, or the direct impl?If the
AandRarguments to theFntraits were associated types, there would be no issue, because the second impl would be illegal -- theFntraits could only be implemented within the same crate as the main type itself.If we had negative where clauses, one could write the manual
FnOnceimpl using a negative where clause:This is basically specialization: we implement
FnOnce, so long as nobody has implementedFnMut, in which case that version wins. This is not necessarily what the author wanted to write, it's just something that would be possible.There is also the (somewhat weird) possibility that one could write a negative where clause on the original blanket impl, kind of trying to say "you can implement FnOnce in terms of FnMut, but only if FnOnce is not implemented already". But if you think about it for a bit this is (a) basically a weird ad-hoc kind of specialization and (b) sort of a logical contradiction. I think the current logic, if naively extended with negative where clauses, would essentially reject that sort of impl, because the negative where clause doesn't hold (FnOnce is implemented, by that same impl).
Note that we don't have to solve this for 1.0 because direct impls of the
Fntraits are going to be behind a feature gate (per #18875).cc @aturon