How would we feel about moving in a direction in which we have distinct PhantomXxx types for different behavior? I.e.:
// These opt out of auto traits. `PhantomNotTrait<T>: !Trait` if `T: !Trait`.
struct PhantomNotSend<T>;
struct PhantomNotSync<T>;
// Alternative name: `PhantomCell`
struct PhantomNotNoCell<T>;
// PhantomCovariant et al from https://github.com/rust-lang/rust/issues/135806
// Acts as if owning a `T` for the purposes of drop checking.
struct PhantomDrops<T>;
// ...etc
IMO these are more readily understandable than having to know arcane library/language details like the semantics of PhantomData<Box<T>>.
For variance, these are implemented on nightly. I think it's more than reasonable to extend this to other markers. Though I'd prefer PhantomCell over the double negative
We discussed this in the libs-api meeting last week. We would prefer a language-level solution based on negative impls which would allow you to explicitly implement !Send or !Sync for a type to disable the implicit trait implementation. We are pushing the lang team to partially stabilize this feature to enable this use case.
Hmmm, my understanding is that a negative impl is a semver promise to never implement the trait. There’s cases where you want to ensure a type is not-Send etc. to allow changing the implementation in the future, but you might relax that. I guess this could be a related impl ?Send kind of feature, but it’s not covered by the existing negative impls.
struct NotSend;
impl !Send for NotSend {}
struct Foo {
// prevents Foo from automatically being Send,
// but we're free to remove it later
marker: NotSend,
}
Now, this is sort of back where we started with marker types, but the local definition at least makes it quite obvious.
Yeah, that's sometimes called "reserved impls", and we would very much like it to exist as well.
I want to replace on_unimplemented with it, for example -- then if you try to use it we could say "That's not implemented today, but the crate has reserved the possibility to implement it in future. Here's the doc-comment they wrote about it".
So I was about to say that this is not a new idea, but your idea seems actually different. You don't just want PhantomNotSend (a non-generic type that is never Send); you want to express something conditional on some other type T.
IMO the right way to do that is with impls, that seems a lot less magical than the types you are proposing:
PhantomData<dyn Every + AutoTrait + ButForTheOne + WeDoNotWant>
This is already usable, and also, imho, rather ugly; I guess the ergonomic approach would have to require a magic type:
PhantomNot<dyn AutoTrait>
for instance?
Regarding negative impls,
already shows that it is possible to make this promise of never-to-impl a trait in stable Rust.
As a matter of fact, the docs of that crate get to showcase how, currently, stuff such as PhantomPinned being "only ?Unpin" w.r.t. coherence is actually limiting.
So the solution here, for now, could be to allow writing proper negative impls, at least for non-pub types (if we are that worried about library authors misusing negative impls in public APIs).
unsafe impl Sync for Foo where for<'a> [()]: Sized {}
(only with an unicode symbol instead of 'a for the lifetime to make a name collision unlikely and with a fully qualified path to Sized)
With the HRTB the trivially false bound [()]: Sized is not caught by the compiler at the definition site, but only once the bound is evaluated when trying to check if Foo implements Sync.