Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tracking issue for negative impls #68318

Open
2 tasks
nikomatsakis opened this issue Jan 17, 2020 · 46 comments
Open
2 tasks

Tracking issue for negative impls #68318

nikomatsakis opened this issue Jan 17, 2020 · 46 comments
Labels
A-trait-system Area: Trait system B-experimental Blocker: In-tree experiment; RFC pending, not yet approved or unneeded (requires FCP to stablize). B-unstable Blocker: Implemented in the nightly compiler and unstable. C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC F-negative_impls #![feature(negative_impls)] needs-rfc This change is large or controversial enough that it should have an RFC accepted before doing it. S-tracking-impl-incomplete Status: The implementation is incomplete. S-tracking-needs-summary Status: It's hard to tell what's been done and what hasn't! Someone should do some investigation. T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@nikomatsakis
Copy link
Contributor

nikomatsakis commented Jan 17, 2020

Generalized negative impls were introduced in #68004. They were split out from "opt-in builtin traits" (#13231).

Work in progress

This issue was added in advance of #68004 landed so that I could reference it from within the code. It will be closed if we opt not to go this direction. A writeup of the general feature is available in this hackmd, but it will need to be turned into a proper RFC before this can truly advance.

Current plans

Unresolved questions to be addressed through design process

  • What should the WF requirements be? (Context)
  • Should we permit combining default + negative impls like default impl !Trait for Type { }? (Context)
@nikomatsakis nikomatsakis added C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC T-lang Relevant to the language team, which will review and decide on the PR/issue. labels Jan 17, 2020
@jonas-schievink jonas-schievink added B-unstable Blocker: Implemented in the nightly compiler and unstable. A-trait-system Area: Trait system labels Mar 29, 2020
@kennytm kennytm added the F-negative_impls #![feature(negative_impls)] label Apr 30, 2020
@pirocks
Copy link

pirocks commented Nov 21, 2021

Odd possibly off topic question about this. The following compiles:

#![feature(negative_impls)]

pub struct Test{

}

impl !Drop for Test {}

fn foo(){
    drop(Test{})
}

Should it?

@Allen-Webb
Copy link

Odd possibly off topic question about this. The following compiles:

#![feature(negative_impls)]

pub struct Test{

}

impl !Drop for Test {}

fn foo(){
    drop(Test{})
}

Should it?

I would say no, because if you wanted to shift the error to runtime you could implement Drop as:

pub struct Test{

}

impl Drop for Test {
    fn drop() {
        panic!("Do not drop Test.")
    }
}

I generally like to catch anything at compile time that can be caught at compile time.

@gThorondorsen
Copy link

According to the documentation

This function is not magic; it is literally defined as

pub fn drop<T>(_x: T) { }

As a trait bound, T: Drop means that the type has defined a custom Drop::drop method, nothing more (see the warn-by-default drop_bounds lint). It does not mean that T has non-trivial drop glue (e.g. String: Drop does not hold). Conversely, T: !Drop only says that writing impl Drop for T { … } in the future is a breaking change, it does not imply anything about T's drop glue and definitely does not mean that values of type T cannot be dropped (plenty of people have tried to design such a feature for Rust with no success so far).

(If you did not already know, drop glue is the term for all the code that std::mem::drop(…) expands to, recursively gathering all Drop::drop methods of the type, its fields, the fields of those fields, and so on.)

Yes, the Drop trait is very counter-intuitive. I suggest extending drop_bounds to cover !Drop as well. Maybe even mentioning !Drop at all should be a hard error for now (neither impls nor bounds make sense IMO). Can @nikomatsakis or anyone else add this concern to the unresolved questions section in the description?

@rMazeiks
Copy link

Not sure if this is the right place to report a bug with the current nightly implementation, but a negative implementation and its converse seem to "cancel each other out".

For example:

trait A {}
trait B {}

// logically equivalent negative impls
impl<T: A> !B for T {}
impl<T: B> !A for T {}

// this should not be possible, but compiles:
impl A for () {}
impl B for () {}

The above compiles without errors on nightly, though it shouldn't. Removing one of the negative impls fixes the issue and results in an error as expected.

@JohnScience
Copy link
Contributor

JohnScience commented Dec 30, 2021

Allowing negative trait bounds would make my code much better by allowing to express di- and poly-chotomy.

In Rust 1.57.0 the following doesn't compile:

#![feature(negative_impls)]

trait IntSubset {}

impl <T> IntSubset for T where T: FixedSizeIntSubset + !ArbitrarySizeIntSubset {}
impl <T> IntSubset for T where T: !FixedSizeIntSubset + ArbitrarySizeIntSubset {}

trait FixedSizeIntSubset {}

impl<T: FixedSizeIntSubset> !ArbitrarySizeIntSubset for T {}

trait ArbitrarySizeIntSubset {}

impl<T: ArbitrarySizeIntSubset> !FixedSizeIntSubset for T {}

@mwerezak
Copy link

mwerezak commented Mar 13, 2022

Coming here from a compiler error, how do I "use marker types" to indicate a struct is not Send on stable Rust? I don't see any "PhantomUnsend" or similar anywhere in std.

error[E0658]: negative trait bounds are not yet fully implemented; use marker types for now

@rMazeiks
Copy link

@mwerezak I think I've seen PhantomData<*mut ()>. If I understand correctly, *mut () is not Send, so neither is the enclosing PhantomData nor your struct. (Did not test)

PhantomUnsend might be a good alias/newtype for better readability – I had no idea what PhantomData<*mut ()> meant at first :D

@mwerezak
Copy link

@rMazeiks Thanks, I wouldn't have known really what type would be the appropriate choice to use with PhantomData for this.

A PhantomUnsend sounds like a good idea.

@nikomatsakis
Copy link
Contributor Author

I think we should just rule out !Drop -- drop is a very special trait.

@dhardy
Copy link
Contributor

dhardy commented Apr 19, 2022

Can we add "negative super traits" to the unwritten RFC? That is,

pub trait Foo {}

pub trait Bar: !Foo {}

// We now know that the two traits are exclusive, thus can write:
trait X {}
impl<T: Foo> X for T {}
impl<T: Bar> X for T {}

Quote from the Hackmd:

This implies you cannot add a negative impl for types defined in upstream crates and so forth.

Negative super traits should get around this: the above snippet should work even if Foo is imported from another crate.

The potential issue here is that adding implementations to any trait exported by a library is technically a breaking change (though I believe it is already in some circumstances due to method resolution, and also is with any other aspect of negative trait bounds).

@nikomatsakis
Copy link
Contributor Author

I would prefer to leave that for future work, @dhardy -- I'd rather not open the door on negative where clauses just now, but also I think that it'd be interesting to discuss the best way to model mutually exclusive traits (e.g., maybe we want something that looks more like enums).

@dhardy
Copy link
Contributor

dhardy commented Jan 28, 2025

I think we should just rule out !Drop -- drop is a very special trait.

I found a use case for this while working on async-local; for types that don't impl Drop, the thread_local macro won't register destructor functions, and the lifetime of these types can be extended by using Condvar making it possible to hold references to thread locals owned by runtime worker threads in an async context or on any runtime managed thread so long as worker threads rendezvous while destroying thread local data. For types that do impl Drop, they will immediately deallocate regardless of whether the owning thread blocks and so synchronized shutdowns cannot mitigate the possibility of pointers being invalidated, making the safety of this dependent on types not implementing Drop.

@Bajix This sounds like a hack around lifetimes. Is there any guarantee that accessing a !Drop value held by a thread is valid simply because the thread hasn't exited yet?

I'm asking because, once specialization is available, it may make sense to let every type implement Drop. Semantically this is simpler and theoretically it should be no different (note that Rust does not guarantee that drop will be called on thread exit).

@Jules-Bertholet
Copy link
Contributor

Jules-Bertholet commented Jan 28, 2025

Note that “does not implement Drop” does not mean “has a trivial destructor”, if the type has a Drop-implementing field.

It may make sense to let every type implement Drop.

I agree, but I don't think specialization is necessarily required. I propose this at rust-lang/rfcs#3762 (comment)

@oli-obk
Copy link
Contributor

oli-obk commented Jan 28, 2025

Please start a thread for !Drop, it seems to require some discussion and tracking issues are not very well suited for that

@clarfonthey
Copy link
Contributor

clarfonthey commented Jan 29, 2025

Please start a thread for !Drop, it seems to require some discussion and tracking issues are not very well suited for that

https://rust-lang.zulipchat.com/#narrow/channel/213817-t-lang/topic/negative.20impls.3A.20.60!Drop.60/near/496452885

Also, FWIW, I'm treating my earlier comment as a list of current questions, so, any team members are free to edit it to update the list if necessary: #68318 (comment)

Or just move it to the issue description!

@traviscross
Copy link
Contributor

@rustbot labels -I-lang-nominated

In lang triage today we reviewed this and answered the three questions outstanding for us:

  1. The implication here is that the lang team is okay with just a stabilisation report and an FCP for this, but since there was also a linked draft RFC, it's worth checking whether the request is to make a stabilisation report or an actual RFC for this.

We want to see an RFC for this. There is enough to document here that we felt this warranted.

  1. When it comes to stabilising this feature, should conditional negative impls be put under a separate feature gate, or just removed entirely for now? Not sure what the consensus is here.Since it's been over a year since folks have posted here, I'm assuming that nobody is currently busy with this, and I would like to help at least work on cleaning up the details here, but I also am fine not doing so if someone else is still working on it.

Our feeling was that it was fine to just remove conditional negative impls for now. Though this is somewhat of an implementation question; I doubt we'd object as a lang matter to these staying around under a separate feature gate.

  1. Is impl ?Trait considered a hard requirement of stabilisation, or is it something that's simply desired as a future extension?

We didn't see this as a hard requirement and felt it was fine to treat this as a future extension.

@rustbot rustbot removed the I-lang-nominated Nominated for discussion during a lang team meeting. label Feb 26, 2025
@clarfonthey
Copy link
Contributor

Thank you for clarifying everything! This issue should probably be tagged as needs-rfc then, assuming I understand the issue tags correctly.

@traviscross traviscross added needs-rfc This change is large or controversial enough that it should have an RFC accepted before doing it. B-experimental Blocker: In-tree experiment; RFC pending, not yet approved or unneeded (requires FCP to stablize). labels Feb 26, 2025
@compiler-errors
Copy link
Member

I'm working on splitting out negative impls into a separate feature gate (arbitrary_negative_impls), and just wanted to note that it's not possible to remove conditional negative impls from the standard library since we rely on them for coherence.

@Jules-Bertholet
Copy link
Contributor

since we rely on them for coherence.

Where? Could you please post a link?

@compiler-errors
Copy link
Member

compiler-errors commented Feb 27, 2025

@lcnr
Copy link
Contributor

lcnr commented Feb 27, 2025

after a quick chat with @compiler-errors I would like to understand the "no conditional impls" restriction outside of disabling auto trait implementations.

I do not believe negative impls are the correct feature to disable builtin auto trait impls because negative impls provide an API guarantee which is most likely not what people actually intend to do.

Instead of adding impl ?Send for MyType which is confusing, has a builtin-attribute based approach been considered:

#[disable_builtin_impl(Send)]
struct ThisIsNotSend(u32);

@traviscross
Copy link
Contributor

traviscross commented Feb 27, 2025

In our lang discussions, I gather that we all agreed that negative impls are not the correct way to disable those impls in general, exactly because of the API guarantee you mention. That's why we are interested in seeing some way to do that exist eventually (other than adding e.g. PhantomNotSend fields). As far as I recall, we haven't talked about using an attribute rather than impl ?Tr for .. syntax, but it's harder to see how that would work cleanly for one of the cases that @nikomatsakis mentioned:

// A crate is at v1:
trait Tr {}

// An external crate now does:
struct LocalTy;
impl Tr for Rc<LocalTy> {}

// Later, the original crate wants to add:
impl<T> Tr for T {} //~ Breaking change.

So, what we may want so that the trait's crate can hold space is:

impl<T> ?Tr for T {} //~ Proposed feature.

We discussed how you could almost get this with a blanket impl with an alway-ambiguous WC bound, except that you'd have to impl the trait items.

@nikomatsakis also mentioned:

NM: thinking after: Another motivation I had for ? impls was to avoid excessive inference...

impl AsRef<str> for MyType { } // if you just have this impl, `MyType: AsRef<?X>` might infer that `?X = str`
impl<T> ?AsRef<!> for MyType { } // may add this in the future, don't infer that `?X = str`, here `!` is just playing the role of "some type"

...though I might prefer to have other ways to say this.

@compiler-errors
Copy link
Member

I clarified with @nikomatsakis and wanted to state that I've decided that imposing the "always applicable" restriction for all negative trait impls is a bit arbitrary, but it is still useful for auto traits, since like positive impls, this probably doesn't mean the thing you want it to mean:

struct W<T>(T);

impl !Send for W<i32> {}

Namely, that negative impl also opts-out the built-in positive impl of Send for W<T> even for types like W<i32>. See #93367 for why (I believe this is the right issue).

But for non-auto traits, having "partial" negative impls is actually pretty useful. Or at least it makes no sense to restrict the generality of the feature.

I'll put up a feature that checks that negative impls of auto traits are always applicable.

@nikomatsakis
Copy link
Contributor Author

nikomatsakis commented Feb 28, 2025 via email

workingjubilee added a commit to workingjubilee/rustc that referenced this issue Mar 4, 2025
…negative-impl, r=lcnr

Ensure that negative auto impls are always applicable

r? lcnr (or reassign if you dont want to review)

rust-lang#68318 (comment)
workingjubilee added a commit to workingjubilee/rustc that referenced this issue Mar 5, 2025
…negative-impl, r=lcnr

Ensure that negative auto impls are always applicable

r? lcnr (or reassign if you dont want to review)

rust-lang#68318 (comment)
workingjubilee added a commit to workingjubilee/rustc that referenced this issue Mar 5, 2025
…negative-impl, r=lcnr

Ensure that negative auto impls are always applicable

r? lcnr (or reassign if you dont want to review)

rust-lang#68318 (comment)
jieyouxu added a commit to jieyouxu/rust that referenced this issue Mar 5, 2025
…negative-impl, r=lcnr

Ensure that negative auto impls are always applicable

r? lcnr (or reassign if you dont want to review)

rust-lang#68318 (comment)
jieyouxu added a commit to jieyouxu/rust that referenced this issue Mar 5, 2025
…negative-impl, r=lcnr

Ensure that negative auto impls are always applicable

r? lcnr (or reassign if you dont want to review)

rust-lang#68318 (comment)
compiler-errors added a commit to compiler-errors/rust that referenced this issue Mar 6, 2025
…negative-impl, r=lcnr

Ensure that negative auto impls are always applicable

r? lcnr (or reassign if you dont want to review)

rust-lang#68318 (comment)
Noratrieb added a commit to Noratrieb/rust that referenced this issue Mar 6, 2025
…negative-impl, r=lcnr

Ensure that negative auto impls are always applicable

r? lcnr (or reassign if you dont want to review)

rust-lang#68318 (comment)
compiler-errors added a commit to compiler-errors/rust that referenced this issue Mar 6, 2025
…negative-impl, r=lcnr

Ensure that negative auto impls are always applicable

r? lcnr (or reassign if you dont want to review)

rust-lang#68318 (comment)
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Mar 7, 2025
Rollup merge of rust-lang#137764 - compiler-errors:always-applicable-negative-impl, r=lcnr

Ensure that negative auto impls are always applicable

r? lcnr (or reassign if you dont want to review)

rust-lang#68318 (comment)
github-actions bot pushed a commit to rust-lang/miri that referenced this issue Mar 7, 2025
…impl, r=lcnr

Ensure that negative auto impls are always applicable

r? lcnr (or reassign if you dont want to review)

rust-lang/rust#68318 (comment)
lnicola pushed a commit to lnicola/rust-analyzer that referenced this issue Mar 10, 2025
…impl, r=lcnr

Ensure that negative auto impls are always applicable

r? lcnr (or reassign if you dont want to review)

rust-lang/rust#68318 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-trait-system Area: Trait system B-experimental Blocker: In-tree experiment; RFC pending, not yet approved or unneeded (requires FCP to stablize). B-unstable Blocker: Implemented in the nightly compiler and unstable. C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC F-negative_impls #![feature(negative_impls)] needs-rfc This change is large or controversial enough that it should have an RFC accepted before doing it. S-tracking-impl-incomplete Status: The implementation is incomplete. S-tracking-needs-summary Status: It's hard to tell what's been done and what hasn't! Someone should do some investigation. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests