You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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 traitFoo{}structBar<T>{}impl<T:Copy> !FooforBar<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:
traitFoo<A>{}impl<A,B> !Foo<A>forB{}// 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.
@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> !Sendfor(A,B)whereA: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.
auto traitFoo{}structBar<T>{}impl<T:Copy> !FooforBar<T>{}
The question is, does Bar<Box<T>> implement Foo? 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 type T, we first check for explicit (positive) impls that apply to T. If any are found, then T implements Foo. Otherwise, we check for negative impls. If any are found, then T does not implement Foo. If neither positive nor negative impls were found, we proceed to check the component types of T (i.e., the types of a struct's fields) to determine whether all of them implement Foo. If so, then Foo is considered implemented by T.
"first check for explicit (positive) impls": none are found; however if Foo and Bar are public then a downstream crate could provide an impl for a local type Local
"Otherwise, we check for negative impls": Box<T> is not Copy so none are found, though again a downstream crate might provide one for Local
we proceed to check the component types: there are none, which presumably means that Foo is considered implemented by Bar<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?)
Activity
codehag commentedon Nov 16, 2020
nikomatsakis commentedon Mar 31, 2022
Example of us enforcing a similar rule for drop check:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=fd4485ceb5a5145a1c77e336d778aecb
Note that the
Dropimpl is allowed withT: Debug, because that bound appears on the struct.nikomatsakis commentedon Mar 31, 2022
Relevant code in drop-check is here:
rust/compiler/rustc_typeck/src/check/dropck.rs
Lines 34 to 35 in 244a73c
nikomatsakis commentedon Mar 31, 2022
@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_implcode to be generic over the trait and not specific toDrop.We will want to extend it, also, because I don't want you to be able to do things like
In other words, we can kind of define what the "type parameters and predicates" are for other types like tuple types,
&Ttypes, 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.nikomatsakis commentedon Mar 31, 2022
This is blocking stabilization of negative impls, although we could opt to just stabilize negative impls for non-auto-traits.
lcnr commentedon Apr 1, 2022
i am currently looking into reworking
dropck, cc #95309, but i also want to generalize the predicate part to get rid of SimpleEqRelationwhile I am at it I could just write #74648 take 2
pitaj commentedon Feb 25, 2023
Negative conditional impls enables the following pattern as well. Figured I should put this here
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=752114f2762321fa148318468b7b4352
dhardy commentedon Jun 30, 2023
What's the disagreement? According to the RFC:
FooandBarare public then a downstream crate could provide an impl for a local typeLocalBox<T>is notCopyso none are found, though again a downstream crate might provide one forLocalFoois considered implemented byBar<Box<T>>Aside: does
Bar<Local>: Foo? IfLocal: Copywe have an explicit!Fooimpl; in this case any impl ofFooor!FooforBar<Local>in the downstream crate would be a conflict. (This is no different than positive impls.)As I mentioned in #13231,
auto traitas used for e.g.SendandUnpinis 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?)