forbid conditional, negative impls
nikomatsakis opened this issue ยท 8 comments
We currently permit negative impls (feature-gated, #68318) for both auto- and regular traits. However, the semantics of negative impls are somewhat unresolved. The current trait checker's implementation in particular does not work well with "conditional" negative impls -- in other words, negative impls that do not apply to all instances of a type. Further, the semantics of such impls when combined with auto traits are not fully agreed upon.
Consider:
auto trait Foo { }
struct Bar<T> { }
impl<T: Copy> !Foo for Bar<T> { }
The question is, does Bar<Box<T>>
implement Foo
? There is some disagreement about what would be expected here.
As a temporary step, the plan is to forbid impls of this kind using similar logic to what we use for Drop
impls. There was a PR in this direction #74648.
Another similar step would be to forbid negative impls for traits that have multiple generic parameters:
trait Foo<A> { }
impl<A, B> !Foo<A> for B { } // error
There is no particular reason that we can't support multiple parameters, but I suspect that the drop logic is not designed to handle cases like this.
Related issues:
Example of us enforcing a similar rule for drop check:
Note that the Drop
impl is allowed with T: Debug
, because that bound appears on the struct.
Relevant code in drop-check is here:
rust/compiler/rustc_typeck/src/check/dropck.rs
Lines 34 to 35 in 244a73c
@spastorino do you plan to look into this? (If so, I can assign it to you.)
As we said in our call, the obvious next step is to refactor the check_drop_impl
code to be generic over the trait and not specific to Drop
.
We will want to extend it, also, because I don't want you to be able to do things like
impl<A, B> !Send for (A, B) where A: Copy { }
In other words, we can kind of define what the "type parameters and predicates" are for other types like tuple types, &T
types, etc, and ensure that the where-clauses/predicates used by the negative impl are limited to those. But the first step would just be to make it work for structs.
This is blocking stabilization of negative impls, although we could opt to just stabilize negative impls for non-auto-traits.
i am currently looking into reworking dropck
, cc #95309, but i also want to generalize the predicate part to get rid of SimpleEqRelation
while I am at it I could just write #74648 take 2
Negative conditional impls enables the following pattern as well. Figured I should put this here
Consider:
auto trait Foo { } struct Bar<T> { } impl<T: Copy> !Foo for Bar<T> { }The question is, does
Bar<Box<T>>
implementFoo
? There is some disagreement about what would be expected here.
What's the disagreement? According to the RFC:
Intuitively, to check whether a trait
Foo
that contains a default impl is implemented for some typeT
, we first check for explicit (positive) impls that apply toT
. If any are found, thenT
implementsFoo
. Otherwise, we check for negative impls. If any are found, thenT
does not implementFoo
. If neither positive nor negative impls were found, we proceed to check the component types ofT
(i.e., the types of a struct's fields) to determine whether all of them implementFoo
. If so, thenFoo
is considered implemented byT
.
- "first check for explicit (positive) impls": none are found; however if
Foo
andBar
are public then a downstream crate could provide an impl for a local typeLocal
- "Otherwise, we check for negative impls":
Box<T>
is notCopy
so none are found, though again a downstream crate might provide one forLocal
- we proceed to check the component types: there are none, which presumably means that
Foo
is considered implemented byBar<Box<T>>
Aside: does Bar<Local>: Foo
? If Local: Copy
we have an explicit !Foo
impl; in this case any impl of Foo
or !Foo
for Bar<Local>
in the downstream crate would be a conflict. (This is no different than positive impls.)
As I mentioned in #13231, auto trait
as used for e.g. Send
and Unpin
is more complex than required for general opt-out marker traits, hence should probably not be used. (Would removal of the rules concerning component types and opt-in solve the apparent issue here?)