RFC: Ghost Busting

🖼 Rendered
📝 Summary

Defines unused type parameters to act as if they are logically owned and covariant.

Introduces phantom T pseudo-fields which improves the ergonomics of changing
variance and drop checking behavior when it is needed. A phantom T field also has
the same behavior with respect to auto traits as PhantomData<T> does.

PhantomData<T> is redefined as struct PhantomData<T: ?Sized>; and deprecated.

The lang item phantom_data is removed.

Derive macros for derivable standard library traits will take advantage of statically known
phantom types and fields to generate more permissive impls.

That's it folks; Happy 👻👤!
💖 Thanks
Thanks to @nikomatsakis and @eddyb for interesting discussions.

My first thought here is that the RFC is missing a section convincing me that unused type parameters and phantom data are such a common problem that adding special syntax is justified.  For example, doesn't this force macros that consume struct definitions to understand the special phantom pseudo-keyword?
In particular, I'm not convinced that phantom: ::std::marker::PhantomData<T> is so substantially worse than phantom T would be.  Certainly if (1) is a big issue, then one could argue for prelude inclusion instead -- PhantomData is a pretty unique name -- for far less impact than new syntax.

(3) acknowledge the field by adding an expression PhantomData when a value of the type is to be constructed.

This one's more interesting to me, but I think it's largely applicable to many ZSTs, not just PhantomData.  Like if I make a pub struct Foo(()); to keep it from being externally constructed, I need to pass the ().  There are a bunch of possible alternatives that would work for this, like a trait on types that are only ever set to their default, an attribute on the field to set to default, field initializers, allowing them to be unnamed (like https://github.com/rust-lang/rfcs/pull/2102 since the type has no fields, and thus "disappears" other than its type), etc.

  And I think it needs a more realistic example than struct Label<T>;, since it seems that one would be handled just fine by type Label<T> = PhantomData<T>;  And the Vec example certainly can't derive most of its traits.

For example, doesn't this force macros that consume struct definitions to understand the special phantom pseudo-keyword?

For all practical intents and purposes, yes.

In particular, I'm not convinced that phantom: ::std::marker::PhantomData<T> is so substantially worse than phantom T would be.

I believe that (3) makes it substantially worse. If we can provide a good and alternative way to solve (3) together with (1) (with prelude inclusion..), then I believe they are on par.

Certainly if (1) is a big issue, then one could argue for prelude inclusion instead -- PhantomData is a pretty unique name -- for far less impact than new syntax.

That's a good and cheap alternative, but it does not solve 2. and 3. (the latter is the most annoying part to me..). I've added this suggestion to the list of alternatives.

a bunch of possible alternatives that would work for this, like a trait on types that are only ever set to their default, an attribute on the field to set to default, field initializers, allowing them to be unnamed (like #2102 since the type has no fields, and thus "disappears" other than its type), etc.

Can you expand on these?

Wouldn't your #2353 handle this just fine?

It would indeed, but it would be less automatic since you'd have to acknowledge the phantomness doubly on the type parameter(s) (or the type) and with a PhantomData<P> field. In cases where you don't need phantom T to change variance, the advantage of struct Label<T>; is 0 steps vs. 2 steps with PhantomData<T> + #[derive_no_bound].
What I like about unused parameters => phantom P is that they together form a neat consistent package.

but it does not solve 2

I think that "add a pseudo-field using phantom" and "add a read field using PhantomData" are essentially the same for (2).  But of course I like the "you have to mention all your parameters somehow to say what you wanted" behaviour, so I'm sure we have different perspectives here.

Can you expand on these?

Sure, though be warned this is an unfiltered splat of things -- I haven't thought about the details for most, and some of them might just be just terrible.

trait ImplicitDefault : Default {} and impl ImplicitDefault for (), then struct initializers add x: ImplicitDefault::default() for any field x that wasn't specified.  (Could also change elseless-if desugar to else { ImplicitDefault::default() }, but that's a different conversation.)
struct Foo(#[always_default] ());, allowed only on a suffix of the fields for tuple-structs, prohibits providing a value in initializers and always uses Default::default()
struct Foo(() = ());, along the lines of https://github.com/rust-lang/rfcs/pull/1806
struct Foo { _: () }, the struct still contains the type, but initializing and reading "inlines" the fields -- all zero of them -- so there's nothing to need to specify in the initializer.

@scottmcm Ah yes, (2) only goes away morally when you don't have to acknowledge unused type parameters.

But of course I like the "you have to mention all your parameters somehow to say what you wanted" behaviour, so I'm sure we have different perspectives here.

I guess the central question wrt. eliding unused parameters is if we believe that we help or hurt users substantially by not/throwing a "you have to use the parameter and think about variance always" error in their face.
When not dealing with unsafe code, the importance of invariance/not shouldn't even arise and owned covariance is the right thing; I think this is very common due to "unconstrained type parameters" when trying to impl a trait, so you add a type param and now you have to add PhantomData also. This seems quite the unergonomic experience when zero unsafe code is involved. Personally I think we should avoid penalizing safe code for the worries of unsafe code.
I'd also like to note that even if you get "unused type parameter" thrown in your face, some people might be inclined just to casually add PhantomData<P> (assuming P is the type parameter) without giving it much thought. Furthermore, variance is non-trivial and incredibly easy to get wrong even if you think about it, a case in point is:

pub struct Id<S, T>(::std::marker::PhantomData<(*const S, *const T)>); (covariance => we can do transmute_lifetime in safe Rust, https://docs.rs/refl/0.1.2/refl/struct.Id.html#a-note-on-variance-and-safety). Defining Id<S, T> this way was actually done in the wild.

trait ImplicitDefault : Default {}

I think that's essentially https://github.com/Centril/rfcs/blob/rfc/ghost-busting/text/0000-ghost-busting.md#alternative-allow-filling-unspecified-fields-with-default
I also got the idea from #1806.

struct Foo(#[always_default] ());

Seems like a variation on ImplicitDefault. Interesting, but seems quite niche tho.

struct Foo { _: () }

I think this was @eddyb's idea? This would be: struct Foo<T> { _: PhantomData<T> } and work for any ZST?

I see two possible intents of a user that gets the unused type parameter error:

They forgot to use it (forgot to add a field) or removed the use (removed the field) and forgot to remove the parameter.
They meant to use PhantomData.

The status-quo optimizes the experience of writing definitions for the first case, this RFC optimizes it for the second case. Which is better depends on what we believe the common case is. I believe that 1 is the common case and the status-quo is better, this RFC could retain the benefits of the status-quo by making the phantom T annotation mandatory.

@leodasvacas I think that's a good analysis. I'd like to add that I think that 1. is more common when "in-transition" (refactoring) as opposed to just starting out with a new type. That a field is missing will be very noticeable when you try to use that field but notice that it isn't there.
I'm certainly open to only keeping phantom T if we believe that is the right balance.

+1 to defaults for unused type parameters (while keeping unused_type_parameters as a usual unused lint that can be turned off during prototyping) (this also should be enough to get rid of phantom_data lang item).
-1 to the new special syntax and phantom fields sometimes behaving as normal fields and sometimes not.

@petrochenkov Might perhaps be better to make #[allow(unused_type_parameters)] the default and then #[deny(unused_type_parameters)] after?

-1 to the new special syntax and phantom fields sometimes behaving as normal fields and sometimes not.

Heh... and I was hoping to not get caught between two camps wanting to change things in opposite directions =P
What in particular are you referring to wrt. "sometimes [..] sometimes not"?

@Centril

What in particular are you referring to wrt. "sometimes [..] sometimes not"?

Privacy treatment, for example (phantom is a field) and construction/pattern matching (phantom is not a field).

@petrochenkov

Privacy treatment, for example (phantom is a field) and construction/pattern matching (phantom is not a field).

Ah; Why tho is this a problem (and what category of problem, teachability, consistency, compiler complexity, ..)?
Personally, I think this discrepancy falls naturally out of the way PhantomData<T> is used today - you never care about / want to pattern match on a PhantomData, and adding it in construction is a pain, but privacy is a matter of correctness so you have to care?

I agree with @scottmcm that I don't feel like specific syntax is required to replace PhantomData, I don't believe removing PhantomData is a thing worth pursuing.
If the community decides it is worth pursuing though, I don't think the way described here is a bad way to do it.

I don't like the general idea of this PR to add a custom keyword to the language for a niche feature... Rust actually came from that place where it had tons of language features. They then gradually moved to the standard library. Now we see built in stuff re-appearing like the ? operator or now this PR....
Adding PhantomData to the prelude should be enough. I side with @scottmcm and @nox here.

I don't like the general idea of this PR to add a custom keyword to the language for a niche feature...

I don't agree that phantoms are niche; I find that they show up plenty when using a little more advanced type features.

Now we see built in stuff re-appearing like the ? operator or now this PR....

And ? was added for good reason, it improved ergonomics over try!(..) a lot. And PhantomData has both ergonomical and technical problems (deriving). Improving phantoms by introducing phantom T was also suggested in 2014, before 1.0. For me, the way phantoms dealt with are really about the behavior of the language, and relegating it to libraries worsens ergonomics.

Adding PhantomData to the prelude should be enough.

Certainly open to that if we decide that is the better route. I'd like for us to also find a solution that solves the greatest ergonomics problems wrt. phantoms: the need to provide marker: PhantomData, in literals...

I am very very sympathetic with removing the need for declare=ing a PhantomData<..> member. I use phantom type parameters a lot for various things like math types or Ids and the mandatory member makes it impossible to have a simple and clean public interface.
Take euclid for instance: I would love for people to be able to write v = Vector2D { x: 1.0, y: 2.0 }; but has to be either v = Vector2D { x: 1.0, y: 2.0, _marker: PhantomData }; everywhere or force a constructor like Vector2D::new(1.0, 2.0); on everyone.
My experience so far has been that for every use of phantom type parameters I have had to trade a safer API with degraded ergonomics, and I would use them a lot more if it wasn't this way.
I understand that the cost of adding a keyword is big and maybe too big to fix this. however, some of the things this RFC wants to solve could maybe be achieved in other ways. Maybe with something like attributes?
#[phantom(T)]
Vetcor2D<T> {
  x: f32,
  y: f32,
}
type ScreenVector = Vector2D<ScreenSpace>;
type WorldVector = Vector2D<WorldSpace>;

let a = WorldVector { x: f32, y: f32 };
let WorldVector { x, y } = some_function();
// etc. Notice how nice it is to not deal with the extra member here.

This kind of thing is painless to do in C++.
I believe that removing the need to add members for unused type parameters is very much worth pursuing and could be done in a backward-compatible way without the cost of adding syntax, I am mentioning attributes but there probably many other ways.

@nical AFAIK you can't build structs from a type alias, so even if PhantomData was unneeded you still wouldn't be able to do WorldVector { x, y }. That is a separate issue and doesn't change you point though.
Did you ever consider doing things like that?
struct Unit<U>(PhantomData<U>);

enum World {}
const WORLD: Unit<World> = Unit(PhantomData);

Vector2D { x, y, u: WORLD }

I think this requires as many imports as what you described with #[phantom], for almost no additional syntax boilerplate.

@nical

I understand that the cost of adding a keyword is big and maybe too big to fix this.

Actually, I think the changes to the grammar are larger if you want to go the attribute route, since then, you have to allow arbitrary types inside attributes as in #[phantom(<type>)] (AFAIK this is not permitted yet).
If you take a look at the grammar changes here: https://github.com/Centril/rfcs/blob/rfc/ghost-busting/text/0000-ghost-busting.md#grammar, you see that no new keywords are introduced (phantom T is contextual) and that the delta is quite small.
I'd argue that the mental cost is also quite small or even reduced compared to PhantomData<T> since there now are (substantially, I think) fewer places where explicit phantoms turn up.

@quininer, @fstirlitz, @tanriol, can you elaborate on your concerns?
@kennytm, what is confusing you? can I elaborate on something?

@Centril you don't really need arbitrary types inside attributes if you look at @nical's example like your #[derive_no_bound] attribute from RFC 2353, right ?
struct Vector2D<#[phantom] T> {
  x: f32,
  y: f32,
}

@lqd In that particular case you can just write:
struct Vector2D<T> {
  x: f32,
  y: f32,
}

and you don't need an attribute... However, if we consider:
struct Id<A, B> { phantom (*mut A, *mut B) }

and you want to use an attribute as in:
#[phantom((*mut A, *mut B))]
struct Id<A, B>;

then I believe you must change the attribute grammar to accept arbitrary types.

@Centril, sorry, I'm worried mostly about the syntax you've suggested. For me this feels like a very ad-hoc solution. It does not fit my mental model.
Right now my mental model of a struct includes:

fields (which have name, type, privacy, ...)
attributes, including derive-related, both on the struct and on fields
and, separately, impls and trait impls.

The PhantomData type nicely fits the "fields" category and does not stick out, while the syntax you've suggested matches neither fields nor attributes. Honestly speaking, even
    #[phantom] _type: T,

looks better to me. At least it looks like a field (with a special property of "don't need to mention on creation and destructuring").

Speaking about it, may it be the time to look at #1806 (default field values) again? It would likely solve the "need to mention on creation" problem.

Default field values are way bigger in scope and I wish the original RFC for that had been rejected instead of postponed.

@tanriol
#1806 is not mentioned directly in the RFC, but it is what is meant by one of the alternatives. However, I believe @nikomatsakis was against just allowing defaults without at least some acknowledgement that they are being passed. I will mention #1806 explicitly to avoid confusion in the future.

For me this feels like a very ad-hoc solution.

Sorry to hear that :( I tried to design something that fit how phantoms are actually used and what they need. Phantoms are used today for 3 reasons:

To get rid of "unused parameter" errors. This is solved mainly by assuming owned covariance by default
To change the variance. This is solved with phantom T
To also control privacy without adding an extra field (see Id<A, B> example). This is solved by permitting privacy control for phantom T.

The PhantomData type nicely fits the "fields" category and does not stick out

I think that variance, where it needs to be changed, is important and should stick out.

Honestly speaking, even
    #[phantom] _type: T,

looks better to me. At least it looks like a field (with a special property of "don't need to mention on > creation and destructuring").

This is interesting, but has a few problems:

You have to invent a name _type for what should be unnameable.
It is not at all obvious that you can't access a value of type T with self._type. A reasonable reader can easily assume this.

@Centril Consider that as a "-0" about the phantom contextual keyword. Other people have already stated the reasons. I'd prefer the "Alternative: Allow filling unspecified fields with Default".

@tanriol I've added a discussion of your alternative to the RFC text.

Did you ever consider doing things like that?

That's a neat trick. It improves the situation by papering over the phantom data member in a syntactically less disgraceful way. That said, while nicer, this trick doesn't bring the euclid example even close to how nice the same type of code is in other languages.
I'd rather not side-track this discussion too much in the specific case of euclid. The way we are forced to have this PhantomData member everywhere hurts ergonomics in a variety of cases and finding specific ways to paper over each of them only alleviates a fraction of the pain.

I think that variance, where it needs to be changed, is important and should stick out.

I 100% agree with this. I'll add that variance is an unknown concept to the majority of programmers, and explaining to people that they have to deal with this empty unused parameter everywhere "because of how rustc deals with variance" isn't great.

Actually, I think the changes to the grammar are larger if you want to go the attribute route, since then, you have to allow arbitrary types inside attributes as in #[phantom()] (AFAIK this is not permitted yet).

Interesting. I honestly don't have a particular attachment to how this is expressed as long as it is contained within the struct declaration and not spread over all of its uses. There are plenty of variations of the syntaxes proposed here that we can come up with and hopefully there are a few of them that don't complicate rust's grammar in dramatic ways.
There is always the solution you proposed of having a default behavior (unused type T implies phantom(T) which seems to be the majority of cases in my experience), and opt into changing the behavior using Phantom data.
It's not a complete solution but does remove the hassle for the majority of the use cases.

@Centril, two more follow-up thoughts.

I think that variance, where it needs to be changed, is important and should stick out.

That's actually another interesting difference. The PhantomData<T> field is pretty much self-explaining ("this struct contains a T somewhere the compiler can't see"). It's always needed and thus sidesteps the question of variance ("variance by example"). IMO, the default of phantom T can increase the barrier to entry ("What does "covariant by default" actually mean?"). However, I don't know whether this is a benefit (users probably will actually understand variance) or a drawback (they just don't think about variance at all until they find out it was unsound).
So I'm not really sure whether the "default to phantom T" behavior is actually desirable, but I do agree with the need for some solution of "...and then you mention this field every time creating your structure".
About #[phantom],

You have to invent a name _type for what should be unnameable.

I'd consider the name of this field part of its documentation, as with normal fields. It is the shortest explanation of "what is this field here for".

I find it amusing to talk about the default value of PhantomData which is a unit type (ZST) and therefore carries no meaningful value. My favorite solution is along what @scottmcm proposed but more general, allowing the elision of ZSTs entirely from struct literals so we may write Vector2D { x: 1.0, y: 2.0 } or if it's a tuple struct with ZSTs in trailing position we write Vector2D(1.0, y: 2.0) and if all fields are ZSTs you can just write TupleStruct rather than TupleStruct(). And of course that works for enum variants, so Ok(()) is just Ok. This would entail zero changes to the grammar.

"this struct contains a T somewhere the compiler can't see"

This is only true for some use cases of phantom types. Phantom types are often used as some sort of "tag" in order to use the type system to protect you against common mistakes. There isn't necessarily a T somewhere and even when there is one it isn't necessarily owned by the struct.

The PhantomData field is pretty much self-explaining

I very strongly disagree with this. This member only makes sense to someone who understands what variance is, how it matters to rustc, and actually uses phantom types in a way that variance actually matters (when there is indeed a T contained by the struct indirectly somehow).

It's always needed and thus sidesteps the question of variance ("variance by example"). IMO, the default of phantom T can increase the barrier to entry ("What does "covariant by default" actually mean?").

The way I see it is rather the opposite: it isn't always needed but forces you to understand what variance is or blindly accept that PhantomData<T> must be stuffed into the struct members if T isn't used.
In other languages I can add unused type parameters without ever having to learn what variance means. PhantomData increases the barrier for adding unused type parameters.

@tanriol

The PhantomData<T> field is pretty much self-explaining ("this struct contains a T somewhere the compiler can't see").

Not necessarily. std::iter::Empty<T> will never produce a T because the iterator always returns None.

IMO, the default of phantom T can increase the barrier to entry ("What does "covariant by default" actually mean?").

It means that any unused parameter T is counted as if it is owned and that a subtype S can be used in its stead. If people read PhantomData<T> for an unused parameter T they still have to understand that this means owned covariance if they care about a type's internals. If we instead provide a default, then people will not need to think about variance until they are actually dealing with unsafe code which I believe lowers barrier to entry.

they just don't think about variance at all until they find out it was unsound

For variance to cause unsoundness, some use of unsafe has to be involved (AFAIK, since safe Rust should never be able to be unsound / exhibit undefined behavior), and in that case unsoundness really is flowing from the use of unsafe. Using unsafe is usually not done by beginners and once you are dealing with unsafe you really want to read the rustonomicon and talk to more experienced people.

I'd consider the name of this field part of its documentation, as with normal fields. It is the shortest explanation of "what is this field here for".

But the name is never referred to, so why name it?

allowing the elision of ZSTs entirely from struct literals

This feel too "magical" to me. At the very list it should be restricted to ZSTs without private fields. A ZST with a private field can be useful as a "token" that "proves" that you’ve done some work to obtain it, like initializing something.
But then IMO the only use case for elided ZSTs is PhantomData, so I think that allowing unused type parameters (perhaps with some opt-in, whatever the syntax) is preferable.

@Centril: I'd rather have a more general solution which could be applied to any singleton type, as suggested by @scottmcm and others.
Also, excessive emojis. (Emojis actually annoy me in general, but I am willing to tolerate them in moderation.)

@SimonSapin The mental model is that ZSTs are just type information, and by default we completely elide type information in function bodies. I do wonder what example you had in mind with private ZSTs. Another big use case is transforming Ok(()) into Ok, which is a case of ZST values being redundant.

@leodasvacas Yes, and type information can be meaningful. I mean something like this:
mod global_lock {
    pub fn take() -> Locked {…}
    pub struct Locked { private: () }
    impl Drop for Locked {…}
    pub fn do_stuff(_: Locked) {…}
}

ZST elision (if any) must not enable Locked values to be created out of thin air.

@fstirlitz

@Centril: I'd rather have a more general solution which could be applied to any singleton type, as suggested by @scottmcm and others.

I think @SimonSapin's comments on this are aligned with my views and shows why this is not a good idea or why we at least can't do it cavalierly.

Also, excessive emojis. (Emojis actually annoy me in general, but I am willing to tolerate them in moderation.)

I will move the emojis from the title, just for you!

@leodasvacas Beside's @SimonSapin's point about private fields, there is also a problem with eliding (for example) Ok(()) to Ok:  The latter is a function symbol, so we'd have a "type-dependent" name clash there. Since name resolution runs before type inference, this seems challenging to implement. But more importantly, it's also confusing to humans.
Ok() would not have this issue because it's still the same syntactic form, just with some fields omitted. Although it still feels magical...

@Centril Note that PhantomData fields not only impact variance, they also impact auto traits. I don't recall seeing any explicit mention of this, but I was curious whether you've considered how unused type parameters would interact with auto traits? (I assume phantom T would work the same as PhantomData<T> wrt auto traits.)

@rkruppe Yes, @matthewjasper already mentioned this, here https://github.com/rust-lang/rfcs/pull/2357#pullrequestreview-101000063. I just forgot to fix this review =)

I assume phantom T would work the same as PhantomData wrt auto traits.

Yep, that would be the natural consequence for them to be functionally equivalent =)

@rkruppe

The latter is a function symbol, so we'd have a "type-dependent" name clash there.

This reminds me of another benefit of phantom T, that given:
struct Foo<X, Y>(X, phantom Y);

You can coerce Foo into a function pointer of type fn(X) -> Foo<X, Y> which can add further to ergonomics in some situations.

Overall I agree that if the compiler needs to have special knowledge of phantoms as variance annotations in any case (which currently means that PhantomData is a lang item), then it might as well provide a UX over it that is pleasant to interact with. (I'm vaguely reminded of the extern type versus OpaqueData discussion.) Having a language-blessed standard library type instead of actual syntax feels like it is being minimalistic, because it has the usual downsides of minimalism, but it's not clear whether it's also providing the upsides.
(:bike: I wonder if there's something other than phantom we could call it that might be more self-explanatory about its intended meaning and purpose. I can't think of any offhand. At this point "phantom" also has the benefit of familiarity for existing users.)

@glaebhoerl Some 🚲:
I wholeheartedly agree with the point you make about familiarity of phantom - but I also think that the name "phantom" is good because it suggests (at least to me) that this is a "fake T" (T being a type).
But here are some alternatives to phantom T we might want to consider (all of which may be really bad, idk):

fake T, and synonyms: bogus T, fictitious T, phony T
logically_owned T (this one is bad because it is really two words..)
--- synonyms of phantom:
illusory T, imaginary T, fictional T
more: http://www.thesaurus.com/browse/phantom and http://www.thesaurus.com/browse/imaginary

I quite like the word phantom. Similar to what @nox said, for the changes proposed, if it is seen that changing the language really is needed instead of just changing the prelude, I think that phantom T is better than an attribute on struct members. I would also be okay with attributes on structs themselves though that list the phantom types.... Such attributes would seem like the smallest change to the language. So like #[phantom(T, U, V)] struct X<T, U, V>;.

I think phantom should not in fields. I like it more
struct Foo<X, phantom Y>(X);

This is more reasonable when used as fn(X) -> Foo<X, Y>

I think struct Foo<phantom T> is nice, but I don't think any syntax should be introduced for more complex uses of PhantomData<T>, so I would force users to keep adding an explicit field such as _marker: PhantomData<&'a T> if they need such markers.

Is it desirable that users add phantom T even when they want covariance in T simply to prove that they considered the issue?  If so then maybe (a) std should do this everywhere too, and clippy should warn about missing phantom T.
Are there still any uses for PhandomData in terms of ensuring lifetime agreement in places?  If so, maybe it should not be slated for eventual deprecation, or maybe such scenarios always benefit from a custom lifetime tracking type, even if it turns out to be struct MyLifetimeTrackingPhantomData<T: ?Sized>;
There is a proposal for struct Foo { _: T } to provide a unnamed field, so I presume you guys already considered overloading that syntax with struct Foo<T> { _: PhandomData<Whatever<T>> } and inferring the PhandomData value when it does not involve lifetimes?

Should we add a lint that an unused type parameter T is better named _T?
As yet another alternative, what if the unused type parameters in struct definition are accepted only as placeholder _?

We definitely could add #[phantom] attribute for generic type. We have #[may_dangle] already, and we could tweak properties of this phantom generic type as well by giving phantom attribute "arguments".

@tanriol That doesn't make sense to me, T is used, it's just not used in the definition of the type. Having to prefix them with _ would be at odds with all functional programming languages.
@nagisa If we can, I would rather have phantom there as a keyword than an attribute.

@nox I’d err on the side of consistency, and wouldn’t mind the phantom keyword, if we made may_dangle and all the future similar toggles a keyword as well.

I've added discussions of most of my comments and your recent replies to the list of alternatives in the text.

@quininer phantom is not a bound so I'd like to outright strike
struct Foo<X, Y>(X) where phantom(*mut Y);

as confusing users with bounds. With respect to:
struct Foo<X, phantom(*mut Y) Y>(X)

I think that is interesting; but I also think that thinking of phantoms as pseudo fields is right, because they act like fields in all ways except that they are not nameable (with field access syntax or during construction). I also think having them on type parameters is less readable because it causes a lot of rightward drift. I'll however add this to the list of alternatives.

@nox Forcing users to write: _marker: PhantomData<&'a T> seems like an arbitrary choice that leads to bad UX - what is the motivation for this?

@burdges

Is it desirable that users add phantom T even when they want covariance in T simply to prove that they considered the issue?

In practice I don't think it will have this desirable effect; and in safe-only code, you shouldn't even have to consider variance. Speaking only for myself, I'd respond to such an error message by just adding phantom T without thinking of the consequences during prototyping.

Are there still any uses for PhandomData in terms of ensuring lifetime agreement in places?

As PhantomData<T> is functionally equivalent to phantom T, there are no uses for PhantomData<T>.

There is a proposal for struct Foo { _: T } to provide a unnamed field [..]

Yep, this has been considered and discussed by @SimonSapin at: https://github.com/rust-lang/rfcs/pull/2357#issuecomment-370434462, and https://github.com/rust-lang/rfcs/pull/2357#issuecomment-370483771.

@tanriol

Should we add a lint that an unused type parameter T is better named _T

This is certainly interesting - and somewhat orthogonal a question? I don't think it will achieve the goal of making authors think more about variance, instead, I think it will have more of an "OK; not I have to do what the compiler tells me to and I don't know why..".

@nagisa
Sure, permitting types inside attributes as in #[phantom(fn(T) -> T)] is doable, but I believe also a larger change. Furthermore, you won't be able to control privacy with attributes, which can be useful where it matters without having to add a field of type (). There's also the issue of rightward drift which I believe decreases readability. This is really the same as phantom(T) on type parameters as suggested by @quininer but with the #[ and ] tokens added.
To me, #[may_dangle] is much simpler - it is a boolean question, and variance is not. Therefore, consistency with #[may_dangle] should not trump giving the best UX imo. I also think there's somewhat of an overuse of attributes, and wouldn't mind making #[may_dangle] a contextual keyword.

@nox Forcing users to write: _marker: PhantomData<&'a T> seems like an arbitrary choice that leads to bad UX - what is the motivation for this?

I strongly disagree with that. In which language is there a precedent for having an unused parameter T that means something different than "I have a parameter T"? To me, _marker: PhantomData<T> and _marker: PhantomData<&'a T> are completely unrelated things that should not be conflated.

Note that I am not even sure that _marker: PhantomData<&'a T> is needed if we just allow to not usetype and lifetime parameters in a type definition. We could just encode things in the following way:
// Not a lang item anymore because unused parameters are just ok now.
struct PhantomData<T>;

pub struct Foo<'a, T: 'a>(());
pub struct FooWithMarker<'a, T: 'a>(PhantomData<&'a T>);

pub struct Bar<T>(());
pub struct BarWithMarker<T>(PhantomData<T>);

pub struct Qux<'a>(());
pub struct QuxWithmarker<'a>(PhantomData<'a>);

There remains PhantomData<*const T> and PhantomData<*mut T> and other pointer-involving markers, but those are found less frequently in the wild AFAIK.

@nox

I strongly disagree with that. In which language is there a precedent for having an unused parameter T that means something different than "I have a parameter T"? To me, _marker: PhantomData<T> and _marker: PhantomData<&'a T> are completely unrelated things that should not be conflated.

You misunderstand me... The bad part of UX I was referring to is having to provide a name _marker and then in the struct literal _marker: PhantomData, or as outlined in my "3 steps to bad ergonomics" in https://github.com/Centril/rfcs/blob/rfc/ghost-busting/text/0000-ghost-busting.md#improving-ergonomics-and-rapid-prototyping. We can solve the first step by adding PhantomData<T> to the prelude, but the other steps don't seem to have very good solutions.

You misunderstand me...

No, I understood your point, I just disagree with it, and I do not want to see anything else than
phantom T (or something similar with an attribute) at most in the type definition. If you want a fancier marker, you use struct PhantomData<T> in a field.
I feel for @nical's wish for better ergonomics, but my feelings stop at having a PhantomData<T> field, I think fancier phantoms available with custom syntax hurt readability more than it helps writability.
I do not think there is anything to fix for PhantomData<*mut T> etc.

I understand now that my encoding doesn't help for:
struct MyVec<T> {
    // If we omit `PhantomData<T>` then the drop checker thinks `T` is non-owned.
    data: *const T,
    len: usize,
    cap: usize,
}

But to me that sounds more like a reason to bring back the Unique<T> pointer wrapper type than have "fake fields" phantom T:
struct MyVec<T> {
    // Pointer type indicating ownership, itself containing an explicit `PhantomData<T>` marker.
    data: Unique<T>,
    len: usize,
    cap: usize,
}

@nox

But to me that sounds more like a reason to bring back the Unique pointer wrapper type than have "fake fields" phantom T:

This is just an example that helps in explaining things - I'm not suggesting you actually define Vec<T> this way... ^,-

I feel for @nical's wish for better ergonomics, but my feelings stop at having a PhantomData field

I'm sure you agree with this, but I'll point it out anyways: It helps to remember that Rust, as a general purpose language, is used for many different things, and people will have diverse needs. =)

I think fancier phantoms available with custom syntax hurt readability more than it helps writability.

I don't agree with this. I believe readability is improved by phantom T fields because it reduces the noise of _marker: as well as _marker: PhantomData. These parts are not important, and should thus go away and give way to what is important and signaling: the variance (by example).

@Centril

It helps to remember that Rust, as a general purpose language, is used for many different things, and people will have diverse needs.

That's why we shouldn't turn all these needs into language sugar, we'll get diabetes if we serve all these diverse needs with sugar.
Something universally useful across domains like ? can certainly get sugar, but phantom fields mostly targets authors of generic collection libraries (that's not a common case) and even in collection code they represent only very minor portion of code.

@petrochenkov

That's why we shouldn't turn all these needs into language sugar, we'll get diabetes if we serve all these diverse needs with sugar.

😆
This is certainly a good argument in general, but... In this case, however, I believe that variance is a property of the language and that it therefore is suitable to handle it with language sugar while PhantomData gives bad UX.

but phantom fields mostly targets authors of generic collection libraries

Typed EDSLs are another target, but I think that improving things for authors of generic collection libraries is a worthwhile pursuit even if it is only a minority interest. Skimming the main rust repository quickly reveals a lot of cases which could be improved.

Something universally useful across domains like ? can certainly get sugar, but phantom fields mostly target authors of generic collection libraries

This is a fair point, and it underlines that phantom data should not be forced on users of a generic type the way they currently are. If the variance is expressed in the type definition without requiring an extra member, then the syntax (even if unpleasant) is only affecting the author of the generic library and not leaking in all of its uses.

That's why we shouldn't turn all these needs into language sugar, we'll get diabetes if we serve all these diverse needs with sugar.

Please do not do this.

@nical

If the variance is expressed in the type definition without requiring an extra member, then the syntax (even if unpleasant) is only affecting the author of the generic library and not leaking in all of its uses.

The Vector2D (and the "struct with public fields and a type-level tag") doesn't need the phantom sugar, only the default for unused type parameters.
(Btw, why does Vector2D uses PhantomData<U> instead of just U? The tag type can be non-zero-sized?)
So far I have hard time imagining scenario when public fields would need to be combined with "non-default" phantom like PhantomData<fn(U)>` or something.

@petrochenkov My point was that in general I believe it to be a bad thing that the information about variance is communicated through a member because of how it leaks into uses of the type, and also because I find it a really surprising way to achieve its purpose.
Of course if the default PhantomData<T> becomes implicit, it will paper over this problem for a common use case. I would prefer to fix the problem rather than paper over it for common cases, but I agree that if this is what we end up with, we'll be better off than where we are now.
I don't want to get into the details of why euclid is made the way it is made here to keep the discussion focused on the issue at hand, but if you are interested we can follow up on that on irc or by email.

Are there still any uses for PhandomData in terms of ensuring lifetime agreement in places?
As PhantomData<T> is functionally equivalent to phantom T, there are no uses for PhantomData<T>.

I'm thinking about destructuring one struct to pass its PhantomData into another struct as a trck to ensure type agreement on absent types.  It's likely always best to create your own ZST for this though.  And normally the type parameters for whatever function do this anyways.
I'm dubious about this claim that variance overall only arises in unsafe code.  In particular rent_to_own works due to invariance but is entirely safe, although not via PhantomData.  I think the RFC's assertion that covariance appears so frequently in data structures and/or unsafe code suffices for this point.

@burdges

It's likely always best to create your own ZST for this though.

Entirely in agreement with this. Defining a type such as Data.Proxy seems the right way to go to me, which does get a bit more ergonomic with this RFC.

In particular rent_to_own works due to invariance but is entirely safe, although not via PhantomData.

Interesting; will take a closer look later =)

Ignoring the motivation question for now, reusing the field syntax doesn't seem to be the best option.

Consider a braced struct:

struct S<T, U> {
    phantom T,
    field1: *mut T,
    phantom fn(U),
}

Field syntax permits multiple phantom fields. Does it make sense to use multiple phantom fields? Probably not, only their "sum" is important and they can be combined with a tuple if necessary - phantom (T, fn(U)).

Now consider a tuple struct:

struct S<T>(*mut T, phantom T);
struct S<T>(phantom T, *mut T);

Fields in tuple structs are positional. Is the position of a phantom field important? No. Logically it's not a part of the list at all.
It also introduces perceived mismatch in number of parameters/arguments between definition and use of the struct.

Finally consider a unit struct:

struct S<T> {
    phantom fn(T)
}

Here the braced form takes away the "unit constructor" from us and we can't use let s = S; anymore.

If we want to specify variances of type parameters directly, then type parameters themselves are the "right" place.
// The variance is deduced by default for both used and unused parameters,
// but can be specified explicitly.
struct S<#[invariant] T> {
    ...
}

Directly specifying desired variances may be a better alternative for advanced users.
(I personally hope that non-advanced users will never have to deal with variances).
If we want to keep the "variance-by-example" approach, then the "variance specification" is logically a single entity attached to the whole structure, kinda like a where clause.
// Braced struct
struct S<T, U> where T: Clone variance_clause_keyword (T, fn(U)) {
    field1: u8,
    field2: u8,
}
// Tuple struct
struct S<T, U>(u8, u8) where T: Clone variance_clause_keyword (T, fn(U));
// Unit struct
struct S<T, U> where T: Clone variance_clause_keyword (T, fn(U));

Additionally, variance of type parameters is a part of struct's interface, so I'd rather expect it to appear as a part of struct's "signature", rather than be a (possibly private) "field".

@petrochenkov Nice & interesting review.
I'd like to stick with the variance by example method...
Do you have any thoughts on what variance_clause_keyword might be?

It's true inferred variance weakens pub slightly @petrochenkov but if we do not infer variance from non-phantom fields, then afaik all developers must learn about variance, which sounds unrealistic.  If we infer variance from non-phantom fields, then anyone confronting variance must deal with variance inference anyways, so they must already learn everything required to handle variance by example, ala phantom or PhantomData.
In fact, I think variance-by-example is much better pedagogically, even if you want to learn the higher level notion of variance, because it'll help one tie the higher level notion into concrete types.
Also:  Are we even sure type parameter annotations like #[invariant] T actually capture everything we do with PhantomData currently?  It must surely collapse once we consider traits with associated types or even ATCs, as those can only be named in where clauses, but it may fall apart much earlier.
struct Foo<a', T: Fun, P: PointerFamily> {
    phantom &'a T::Output,
    p: P::Pointer<T::Input>
    ...
}

I'd think tests suffice to enforce variance constraints on interfaces too, btw.

IMO variance-by-example in the type definition is a fine way to explain what it does ("as if there was this field") and covers more complex cases (not just PhantomData<T>, unlike annotation on type parameters). I don’t care about the exact syntax. And making the default covariance rather than an error sounds nice, but I’m not particularly attached to that idea if other people are against.
What I do hope to gain from this RFC is to remove is the need to repeat phantom: PhantomData, in struct expressions. That’s just noise.

@petrochenkov Allegedly phantoms are also relevant for auto traits, so #[invariant] and such on type parameters seems like it would be insufficient on its own (which tilts things towards keeping the by-example approach). I'm pretty sympathetic to the idea that variance is part of the type's interface and so belongs outside the curlies (I'm pretty sure I advocated that myself pre-1.0, together with not inferring anything from the body unless the body is completely public, but that's water under the bridge); but since variance is being inferred from the body (whatever we put outside of it would not in general be the complete specification!), there's also some logic to the current approach of putting the remainder of the information inside the body as well.
By the way, we've been talking about structs this entire time; does the use case never come up for enums?

IMO variance-by-example in the type definition is a fine way to explain what it does ("as if there was this field")

Totally not serious 😛
pub struct Interned<T> as if <*const T>(usize);

pub struct Arc<T: ?Sized> as if <T> {
    ptr: NonNull<ArcInner<T>>,
}

pub struct Child<'a> as if <&'a ArchiveRO> {
    ptr: ::ArchiveChildRef,
}

struct Graph<'a, 'tcx, MWF: 'a, P> as if <&'tcx ()>
where
    MWF: MirWithFlowState<'tcx>
{
    mbcx: &'a MWF,
    render_idx: P,
}

pub struct Discriminant<T> as if <fn() -> T>(u64);

enum Foo<T> as if <T> {
   A,
   B,
}

union Bar<T> as if <&'static mut T> {
   a: u16,
   b: u32,
   c: u64,
   d: u128,
}

There is an issue in which someone complained about needing to put PhantomData into every enum variant @glaebhoerl so yes it came up but maybe only as an annoyance.  It must come up for real in FFI situations though too.
We should ask if the omitting=covariance rule is correct with enums too.  We infer variance for the whole enum by considering all field present in any variant, right?  I suspect covariance remains the right default for enums involved in FFI too because *mut T already gives invariance in T.
We might eventually allow non-lifetime subtyping of enum variants though.  Is covariance in T actually right for a subenum type in which the paramater type T does not appear?  We cannot obviously treat the subenum type as if T does not appear because methods for the full enum do involve T, but that kinda sounds less like covariance.  I'm maybe confusing myself because subenums would make contravariance appear more in the first place or something, not sure.
Anyway, I'd think the book should speak clearly and exhaustively about what phantom influences, like variance and subtyping, auto-traits, etc.  Auto-traits are not mentioned in the PhantomData section currently.

Has something like
struct Foo<T> {
    _: *mut T,
}

come up as phantom syntax?

I think that if we want to improve the current marker stuff, we should get rid of the whole "by example" thing entirely. I hear the argument that we need more than #[variant]/#[invariant] etc, for auto traits and whatnot, but the current way of doing it by example IMO is way harder to explain than if we had specific words for specific variances and other peculiarities.
I I don't know about variance and you tell me "just add a PhantomData<...> field to your struct, I would be confused, etc.

If it was solely about variance, I would also be in favor of annotating variance explicitly (when it can't be inferred). But in Rust, there are multiple other concerns that PhantomData is relevant for: dropck and auto traits. "Logically owns a T and will drop it when dropped" is an important annotation for some types, and so is "if Send if T is Send" (and likewise for other auto traits). It may very well be that these are minority use cases, but we can't simply sweep them under the rug entirely by deprecating the "by example" approach completely.

@rkruppe I think we can have the cake and eat it: making explicit annotations on type params to say "logically owned" and "can't dangle" and things like that means PhantomData<T> becomes just a normal unit struct with an unannotated unused type parameter, and thus all existing uses of PhantomData<T> continue to just work like it did all this time.

@glaebhoerl

@petrochenkov I'm pretty sympathetic to the idea that variance is part of the type's interface and so belongs outside the curlies [...] but since variance is being inferred from the body (whatever we put outside of it would not in general be the complete specification!), there's also some logic to the current approach of putting the remainder of the information inside the body as well.

I did some more thinking and... I'm very sympathetic to your view here; I also believe that many users are not ready to handle the information given to them about variance, so it may just be noise in documentation. So when we scale the trade-offs, I believe inside the curlies is the right way to go.

By the way, we've been talking about structs this entire time; does the use case never come up for enums?

When researching for this RFC, I spent some time looking for examples of enums and PhantomData and I can't recall finding a single one. One idea I had was to allow the user to specify phantom variants as in:
enum Foo<'a, T: 'a> {
    phantom &'a T,
    Variant1,
    Variant2,
    ..
}

This would be semantically equivalent to having &'a T inside one (or every, doesn't matter) variant.
Since I couldn't find any use cases for this I scrapped the idea.

@kennytm: It is good that you say:

Totally not serious 😛:

...otherwise I might have run with it 😆

@burdges

We infer variance for the whole enum by considering all field present in any variant, right? I suspect covariance remains the right default for enums [...]

Yes, I believe so.

We might eventually allow non-lifetime subtyping of enum variants though.  Is covariance in T actually right for a subenum type in which the paramater type T does not appear?  We cannot obviously treat the subenum type as if T does not appear because methods for the full enum do involve T, but that kinda sounds less like covariance.  I'm maybe confusing myself because subenums would make contravariance appear more in the first place or something, not sure.

Could you illustrate with an example perhaps?
Are you referring to this idea by @nikomatsakis? http://smallcultfollowing.com/babysteps/blog/2015/08/20/virtual-structs-part-3-bringing-enums-and-structs-together/

Anyway, I'd think the book should speak clearly and exhaustively about what phantom influences, like variance and subtyping, auto-traits, etc.  Auto-traits are not mentioned in the PhantomData section currently.

In full agreement here; The reason I didn't consider auto-traits originally in the RFC was precisely because the rustonomicon and the type documentation said nothing about it; so I was oblivious to this fact.

@phaylon

Has something like
struct Foo<T> { _: *mut T, }

come up as phantom syntax?

This is interesting, but I believe also less legible than phantom *mut T and it possibly conflicts and/or can be confused with the syntax proposed in RFC https://github.com/rust-lang/rfcs/pull/2102. I'll mention it in the alternatives section in a jiffy (as well as the of drawbacks my proposal mentioned by @petrochenkov).

@nox

I think that if we want to improve the current marker stuff, we should get rid of the whole "by example" thing entirely. I hear the argument that we need more than #[variant] / #[invariant] etc, for auto traits and whatnot, but the current way of doing it by example IMO is way harder to explain than if we had specific words for specific variances and other peculiarities.

Not sure I agree that variance by example is harder to explain... saying that something "acts like" or is "logically owned" isn't that far out. I also think that using explicit variance annotations will become incredibly verbose, and so the verbosity will harm readability.

@nordmoen, @Marwes: Are there any concerns of yours I can address somehow?

Potential alternative: bounds which are unused are annotated phantom T, e.g. struct S<phantom T>;. This also would show up on rustdoc.
This makes it so that people have to explicitly clarify that a type parameter is for variance.

Also, I feel that the concept of "phantom" makes less sense once the type PhantomData is removed. Maybe owns T might be better?
Additionally, using a verb might make the below syntax a bit more ergonomic than a field:
pub struct S<'a, T> owns &'a mut T {
    // ...
}

It is good that you say:

Totally not serious 😛:

...otherwise I might have run with it 😆

lol 😄 It does show one issue with variance_clause_keyword that some kind of delimiter must be provided, because in
pub struct Discriminant<T> variance_clause_keyword fn() -> T (u64);

if we don't add some separator after fn() -> T, the parser will see the type fn() -> T(u64) instead

@kennytm

if we don't add some separator after fn() -> T, the parser will see the type fn() -> T(u64) instead

wait... isn't this fine since you'd have to write:
pub struct Discriminant<T> variance_clause_keyword fn() -> T <u64>;

for that? i.e: type constructors are denoted with T<u64>, not T(u64)... (tho this twists my Haskeller brain...!)

@Centril  T(u64) is a valid type (well, an unsized one) in Rust because Fn(A, B) -> C.

Summing up a short conversation with @kennytm on #rust-lang:
The syntax:
struct D<T> owns fn() -> T(u64);

is ambiguous because it may be interpreted as both:
struct D<T> owns (fn() -> T(u64));

and:
struct D<T> owns (fn() -> T)(u64);

This ambiguity can however be resolved by explicitly writing the last snippet when you want the latter and always picking struct D<T> owns (fn() -> T(u64)); when you write as in the first snippet.
However, having ambiguities in the first place is not a good thing, but since it is resolvable in this case, I'd personally be fine with it.

I've included a summary of the recent discussion and introduced alternatives in the RFC now.

@Centril

Are there any concerns of yours I can address somehow?

Just the same concern that has been raised earlier in the thread. Adding a special case such as phantom T fields complicates the syntax for what is a rather marginal ergonomic improvement. PhantomData is in most cases an implementation detail and is not exposed to users of the type that is defined making this mostly an improvement for "library writers" (so to say). This puts the bar quite a bit higher for adding more syntax sugar.

https://github.com/Centril/rfcs/blob/rfc/ghost-busting/text/0000-ghost-busting.md#more-permissive-automatic-deriving

Making deriving more permissive would be great but special casing phantom types misses out on a lot of other instances where derive is not as general as it could be.
#[derive(Clone)]
struct MyArc<T>(Arc<T>);

@Marwes
OK; Thanks for explaining.

[...] rather marginal ergonomic improvement [...]

I think the improvement to ergonomics is non-marginal in cases where you have PhantomData<T>. The marginality I believe is instead due to the fact that phantoms are marginal to begin with.
Most of the improvements from this RFC comes mainly as a result of being able to elide phantom T or PhantomData<T> entirely for unused type parameters.

[...] making this mostly an improvement for "library writers" (so to say).

I think it is important to improve ergonomics of writing libraries (which there are a lot of) so that library writers can focus on more important things. EDSLs and typed ASTs for DSLs can also have phantom types even tho the latter are often not libraries.

Making deriving more permissive would be great but special casing phantom types misses out on a lot of other instances where derive is not as general as it could be.

This particular example is addressed by mine and @nox's RFC https://github.com/rust-lang/rfcs/pull/2353 with #[derive_no_bound].

@Centril @kennytm
The tuple struct ambiguity is long known, this ambiguity is exactly the reason why where clauses for tuple structs are written after fields:
struct S<T, U>(T, U) where T: Clone, U: Clone;

@petrochenkov: Ah right! - Updated the RFC accordingly.

Any variance annotations outside the { } still have the problem that either (a) you still infer variance from real fields, which puts the variance annotations in two places, or else (b) you do not infer variance, thus requiring almost everyone to learn variance annotations.
We can improve (b) slightly but it remains a mess:  You might place all annotations outside the { } by making invariance the default and having external annotations only relax the variance, so onlyowns not owns.  I suppose this avoids everyone needing to learn about variance, but instead now most structs have excessively restrictive variance, which sucks too.  Also, relaxing annotations will tend to relax variance too much, certainly if done as variance-by-example, thus making mistakes common.  We also still require far more people to learn variance, so the savings is minimal.
In the (a) direction, we could make variance an autotrait Owns<T> so that impl Owns<T> for MyType; provides covariance, but variance still pierces APIs everywhere like all autotraits, and folks would still want the phantom T sugar.  I suppose an autotrait version is compatible with any approach to unused type parameters.  Autotraits worsen the interface piercing problem because now folks can use Owns<T> bounds for purposes besides variance.  I suppose autotrait could be added to the alternatives section though, but argue they make interfaces excessively porous.
I do think this RFC improves on PhantomData in fairly concise ways, but all these radical alternatives sound half-baked at best.

I think the improvement to ergonomics is non-marginal in cases where you have PhantomData. The marginality I believe is instead due to the fact that phantoms are marginal to begin with.

That was what I meant. Even if it is a good ergonomic win when you use it, it is just such a rare case that it does not warrant special treatment.

Most of the improvements from this RFC comes mainly as a result of being able to elide phantom T or PhantomData entirely for unused type parameters.

I am mildly positive towards allowing unused type parameters as I have found PhantomData unergonomic. However I also haven't found it to be a big problem as I usually only need to refer to it once in the struct definition and once when constructing the type (and this gets wrapped in a function so it only gets done once).

This particular example is addressed by mine and @nox's RFC #2353 with #[derive_no_bound].

👍 If that RFC were to get accepted it seems to me that it would supersede the deriving improvements propsed here. Might be worth a mention that the special case wouldn't be needed in that case?

@burdges Auto-traits are way to radical for my taste =P

Any variance annotations outside the { } still have the problem that either (a) you still infer variance from real fields, which puts the variance annotations in two places.

Is that a problem in practice? Rustdoc doesn't have to include variance clauses just because it is there in the source code, and so you'd write:
#[derive(Copy, Clone, ..)]
struct Id<A, B>(())
phantom (fn(A) -> A, fn(B) -> B);

I'm considering switching to this syntax due to @petrochenkov (I'm unsure as to whether it was a proposal by them or just for the purposes of discussion...).
@Marwes

Even if it is a good ergonomic win when you use it, it is just such a rare case that it does not warrant special treatment.

I will agree to that it is marginal, but not "such a rare" case. =)

I am mildly positive towards allowing unused type parameters as I have found PhantomData unergonomic.

I might move in this direction to only propose allowing unused type parameters.

If that RFC were to get accepted it seems to me that it would supersede the deriving improvements propsed here.

Yes, mostly, but not entirely 😃. The diff would be:
#[derive(Copy, Clone, ..)]
#[derive_no_bound]
struct Id<A, B> {
    marker: PhantomData<(fn(A) -> A, fn(B) -> B)>
}

vs.
#[derive(Copy, Clone, ..)]
struct Id<A, B> {
    phantom (fn(A) -> A, fn(B) -> B)
}

which requires zero additional user interaction. Perhaps you would say that the former is clearer.
I'm inclined to agree that the hassle is small enough that that particular motivation becomes "Meh." at this point. I'll update the RFC with a discussion on this.

-1 on phantom T inside the braces, for me.  It feels too inconsistent with the rest of the language.  I also don't like how it'd cause a loss of symmetry between the type's definition and its construction.  The examples above with variance_clause_keyword feel very close to right for me, though I kind of wish the variance specification could just be a where clause.  Using the not-serious as if example as inspiration, what about something like one of these?
struct A<T, U>where
    impl: phantom_keyword<T, fn(U)>
{
    ...
}
struct B<T, U>(...) where self: phantom_keyword<T, fn(U)>;
struct C<T, U> where Self: phantom_keyword<T, fn(U)>;
struct D<T, U> where _: phantom_keyword<T, fn(U)>;

It could be that phantom_keyword<T, U> ends up being (T, U) but I think that requires parser changes, whereas the exact type parsing we want already happens between < and > today.  phantom_keyword could also be a built-in trait (say, Phantom), but I'm not sure how much implementation work that would require and how it compares to the alternatives.
I suppose instead of introducing phantom_keyword or adding (T, U) support where necessary, the special bounds definition could be repeated multiple times:
struct A<T, U> where
    impl: T,
    impl: fn(U)
{
    ...
}
struct B<T, U>(...) where self: T, self: fn(U);
struct C<T, U> where Self: T, Self: fn(U);
struct D<T, U> where _: T, _: fn(U);

I think constructibility of the type is an orthogonal concept and should be handled separately from variance specification.  It's been a little while since I've written any Rust so I forget if there's a way to specify constructibility of unit structs already, but if there is not, it doesn't seem like we should try to solve it with the PhantomData overhaul.  I'd write it off as a drawback of deprecating PhantomData, and continue to recommend PhantomData as a workaround for anyone bitten by that particular issue until there's an approved RFC for a better solution.

There is a high bar for adding any new syntax to the language, and I don't see how the proposed syntax here pulls its weight relative to the frequency and magnitude of the problem it intends to solve. Furthermore, if we were going to set out to solve the problem of variance once and for all, we could do way better than the word "phantom", though I don't envy that bikeshed.
"Read my lips: no new syntaxes."

@bstrie

There is a high bar for adding any new syntax to the language, and I don't see how the proposed syntax here pulls its weight relative to the frequency and magnitude of the problem it intends to solve.

I believe this argument has already been made a few times.
While the bar is higher, I'd like to offer the following evidence that the bar might not be as high as you might think and that new syntax for niche use cases has been accepted before (I agree with all of these RFCs):

https://github.com/rust-lang/rfcs/blob/master/text/0218-empty-struct-with-braces.md
https://github.com/rust-lang/rfcs/blob/master/text/0214-while-let.md
https://github.com/rust-lang/rfcs/blob/master/text/0803-type-ascription.md
https://github.com/rust-lang/rfcs/blob/master/text/1219-use-group-as.md
https://github.com/rust-lang/rfcs/blob/master/text/2046-label-break-value.md

Fundamentally tho; I believe that specifying interactions / behavior wrt. fundamental properties of the type system (which variance is) should be done with language features and not library types.

Auto-traits are way to radical for my taste =P

You could restrict using the Owns<T> autotrait as a bound maybe, perhaps to just outside the current crate, but that's also radical.

Any variance annotations outside the { } still have the problem that either (a) you still infer variance from real fields, which puts the variance annotations in two places, or else (b) ..
Is that a problem in practice?

It strikes me as slightly inconsistent because we do handle variance with phantom fields, but..
I suppose your tuple struct examples has slight advantages over placing phantoms inside the tuple.
It also handles unit structs without making them either tuple structs or brace structs.
There is also an inconsistency between doing phantom outside for unit and tuple structs but inside brace structs.

Rustdoc doesn't have to include variance clauses just because it is there in the source code

Yes, I think Rustdoc should hide the phantom annotations since they're no longer actual fields.  Ideally Rustdoc should provide all variance information however, including inferred variance, maybe in mouse over text.
If we do phantom outside both () and {} then what about enums?  Annotating phantom on each variant separately more resembles PhantomData, but we've had discussions that PhantomData worked suboptimally for enums before.

@Centril

While the bar is higher, I'd like to offer the following evidence that the bar might not be as high as you might think and that new syntax for niche use cases has been accepted before (I agree with all of these RFCs):

Some of these are tiny extrapolations of existing syntax (empty struct with braces, while let, use group), so their complexity cost is small. Empty structs with braces arguably even simplifies the language compared to only permitting struct S;. This RFC feels much bigger than that.

Fundamentally tho; I believe that specifying interactions / behavior wrt. fundamental properties of the type system (which variance is) should be done with language features and not library types.

Can you articulate why?
Besides, since PhantomData is a lang item, it's part of the language, not "just" a library type. For all intents and purposes, the question is just the surface syntax programmers use to interact with this concept.

@rkruppe

This RFC feels much bigger than that.

I guess it depends on what type of complexity we are talking about... is it complexity for the language user, or purely technical grammar complexity? I would say that permitting unused type parameters also simplifies the language (for the user). With respect to adding phantom clauses / phantom fields, I think the actual grammar changes are very small and comparable to those RFCs (probably even smaller with phantom clauses). What sticks out in this RFC is the introduction of a new contextual word, but I believe it reduces complexity for users.

Can you articulate why?
Besides, since PhantomData is a lang item, it's part of the language, not "just" a library type.

I have to admit I'm not terribly fond of lang items in general; Specifically wrt. phantoms, you get poor UX if you relegate such interactions to libraries which forces users to interact with other parts of the language (modules/paths, constructors) that they shouldn't have to (in my opinion). The current surface syntax is not very ergonomic.

It's not the size of the diff on the grammar. It's an entirely new kind of "clause" in type definitions. While ultimately the concept is the same as PhantomData fields, it's much more prominent and "loud" because it is basically a new kind of entity in the program/type system.
Having these annotations detached from fields also affects "what a type is" on some level: A struct is no longer just generic parameters, where clauses and fields (and 2/3 of those are basically the same as on many other items), it now also has a fourth major component. This affects the reference, people's mental model, and the compiler implementation. It's basically adding something entirely new to the "ontology" of Rust's type system. That's not quite true because PhantomData is also somewhat magical, but that magic feels more isolated (you special case only one type).
This "loudness" is not without justification: the information is sometimes very important, and arguably under-stated today. And it's probably not avoidable, if we want to give PhantomData a first-class syntax rather than having a special unit struct. But at the same time, that makes this change have more impact on how Rust programs look and how type declarations feel than simply generalizing and edge case in existing productions.

@rkruppe

it's much more prominent and "loud" because it is basically a new kind of entity in the program/type system.

I think this is mostly true of phantom clauses, but much less so for phantom fields.

Having these annotations detached from fields also affects "what a type is" on some level:

This is true on a superficial syntactic level, but not semantically. Variance, auto-trait, and dropck behavior has been part of what a type is for a long time. (as you say, it is not quite true...)

That's not quite true because PhantomData is also somewhat magical, but that magic feels more isolated (you special case only one type).

So one solution that has been floated is to only make PhantomData<T> a pure library type by defining it as pub struct PhantomData<T: ?Sized>; which works because the unused type parameter is seen as covariant, etc. What are your thoughts on this? I think that would keep most of the ergonomics benefits without introducing any surface syntax but rather removing a lang item.

This "loudness" is not without justification: the information is sometimes very important, and arguably under-stated today.

Yes, I agree.

But at the same time, that makes this change have more impact on how Rust programs look and how type declarations feel than simply generalizing and edge case in existing productions.

Feelings are hard to quantify... For those that don't use phantoms to change variance, this will have zero impact on how Rust programs feel. For those that do use phantoms, the information is more prominently / clearly stated.

This is kind of in favour of phantom clauses (i.e. owns T or similar), but I think it'd be very useful for rustdoc to display what phantom types underly types in order to make variance clear to the user.
For example, NonNull<T> owns *const T, T. Because Vec<T> has a field of NonNull<T>, Vec<T> also owns *const T, T.
I think that breaking down this and showing it explicitly in the rustdoc, using whatever syntax is chosen for this, would be a big plus. Which is in favour of such a syntax existing.

@clarcharr
For the 🚲🏡 I think I'm partial to the phantom word as the clause keyword, it feels like a smaller change and the "phantom" word is pretty conventional.

I think that breaking down this and showing it explicitly in the rustdoc, using whatever syntax is chosen for this, would be a big plus. Which is in favour of such a syntax existing.

on this topic @burdges said:

Yes, I think Rustdoc should hide the phantom annotations since they're no longer actual fields. Ideally Rustdoc should provide all variance information however, including inferred variance, maybe in mouse over text.

what are your thoughts on this? My current thinking on how the mouse over would function is that if you hover a type or lifetime parameter, the variance is shown in a tooltip with the text "invariant" / "covariant" / "contravariant", and then "owned" if dropck thinks it is... on auto traits I'm not sure how the info would be presented to the user...

Rustdoc does not present auto-traits with all ordinary traits currently?  Ideally autotraits should be presented somehow, albeit maybe only the absence should be displayed for most autotraits.
There is an advantage to presenting autotraits and variance together in an "inferred" section, but if autotrait are presented like normal traits then they should maybe come last, while the variance makes sense to display with the struct.  Could we do both mouse overs and an "inferred" section so the information appears when printed?
I think the autotrait Owns<T> provides an amusing consistency to the language, like maybe addressing the rustdoc issue.  Yet, they cause two big problems that together sounds fatal: (1) Interface leakage might create problems, as mentioned above. (2) Autotraits are normally frequently used as bounds, but almost never changed, while variance is handled as a bound internally to the type checker, and changed quite frequently by developers.  I suppose this argument against autotraits also supports phantom in clause position.

I just want to note: I think there's big value in any solution that doesn't require macros-by-example to be special cased to handle them. So, the fact that phantom markers are just fields at the moment has an ergonomic advantage over special syntax that deviates from regular syntax. This applies to both special syntax for fields and for type parameters.
So, based on that, macro ergonomics wise it seems that besides the status quo, attributes, special cases of current syntax (_: T) or omission (default variance) with one of the former to choose differently have an advantage.

@burdges The latest rustdoc actually shows auto traits now; it's been implemented.

Example: https://doc.rust-lang.org/nightly/std/marker/struct.PhantomData.html#synthetic-implementations

I see two possible intents of a user that gets the unused type parameter error:

I think there's a third: They didn't actually need that type parameter at all.  (Commonly seen in struct Blah<F:Fn()->R,R>(F);, where the solution is to just remove the unnecessary bound.)

I'm pretty sympathetic to the idea that variance is part of the type's interface

Now this observation I really like.  I think there's possibilities here that address my earlier "-0" (thanks kennytm) thoughts by adding to the change the value that the "struct signature" shows more of these things, sortof like how function signatures are not Bar<'_> to show lifetime-dependencies.

First off, thanks @Centril for a quite thought-provoke RFC :)
I want to echo a few things that have already been said (and then add some that I don't think have):

I have used PhantomData enough to be sympathetic with the ergonomics goal.
I would like minimal noise in the type headers. That makes me dislike attributes or extra keywords in the <T1, ..., TN> list. The fact of the matter is that I have never had to care about the variance properties of a type someone else has written for me.
Along the same lines, I think we should be very careful about adding more and more data into rustdocs. At some point it just becomes this overwhelming blob of data, and all I really wanted was to find the order and types of arguments for some method. I am already kind of annoyed at the "This is a nightly API" messages that take up a lot of space.
I am somewhat against accepting unused type parameters. I think that doing weird things with variance or dropck should be very clearly stated somewhere in the declaration. In particular, for me, usually when I have an extra type parameter it does not mean that I want to be covariant over an implicit phantom field; rather, it usually means I refactored my code and forgot to remove a type parameter somewhere. This sort of implicit phantom field would decrease productivity for me by introducing strange and hard-to-understand bugs. And I suspect that most users of rust are not knowledgeable enough about type theory to know that a weird compiler message about variance really means "dude, you forgot to delete T from the list".
More of a personal preference: I have actually come to really like having a small straight-forward language with all the bells and whistles in libstd. I think that libstd/core is a better place for PhantomData than syntax.
The main ergonomics cost, in my experience, is the annoyance of initializing _phantom fields everywhere the struct is created. Adding an extra import and field in a struct is not a big enough fish to go after, IMHO.

There are a few things that I didn't see discuss when catching up on this thread:

Probably my biggest concern: playing with dropck and variance sounds like a really dangerous game. Are we more likely to introduce unsoundness bugs into the type system by adding this? Also, are we making it easier for users to write subtly unsafe code by making a variance error?
How does this affect unions, trait objects, generic functions? Can their types interact in weird ways with variance?
As I see it the biggest way to improve ergonomics would be to avoid having to initialize _phantom fields. To that end, maybe a much more conservative alternative would be to simply allow omission of initialization for zero-sized fields in struct literals? For example:

struct Foo<T> {
  _phantom: PhantomData<T>,
}

struct Bar(());

fn main() {
  let foo = Foo;
  let bar = Bar;
}

Omission of ZSTs is a bad idea, as lots of libraries have types that are currently ZSTs but aren't guaranteed to be always.
I think that omitting phantom types is a good idea, but omitting all ZSTs is not.

Omission of ZSTs is a bad idea, as lots of libraries have types that are currently ZSTs but aren't guaranteed to be always.

Can you clarify what the problem would be? If you previously didn't have to write out the initializer and now you do that's of course a breaking API change, but if you previously had to write it out with 0 fields and now you have to add more, that also is.
Presumably the fleshed-out version of the proposal would be more like "the compiler automatically inserts the literal to construct the ZST, and then checks that for errors normally", e.g. if the ZST had private fields you'd get a privacy error. (Inventing ZST values "out of thin air" would of course be a bad idea because it would violate the soundness of APIs.)

Omission of ZSTs is a bad idea, as lots of libraries have types that are currently ZSTs but aren't guaranteed to be always.

Can you clarify what the problem would be?  If you previously didn't have to write out the initializer and now you do that's of course a breaking API change, but if you previously had to write it out with 0 fields and now you have to add more, that also is.

I think the problem is the opposite of that. Suppose I export a type:
pub struct Foo

You (an external user) can construct that with Foo.
But if I change it to
pub struct Foo {
    somedata: usize,
}

All of a sudden your code breaks.
BUT I would argue that your code already breaks if you add something to a struct in this way, so I don't see it as making anything worse.

@mark-i-m

That makes me dislike attributes or extra keywords in the <T1, ..., TN> list.

I agree; we should avoid the rightward drift as much as possible. Fortunately, phantom clauses and fields do not add any extra attributes or keywords in that list.

Along the same lines, I think we should be very careful about adding more and more data into rustdocs.

This is why I think hiding the information behind tooltips and hidden-by-default sections is a good strategy to only show the data to those that care.

I am somewhat against accepting unused type parameters. I think that doing weird things with variance or dropck should be very clearly stated somewhere in the declaration.

I think overwhelmingly, PhantomData<P> for a type parameter P is the sane default since it arises frequently due to traits and the "unconstrained type parameter" error.

rather, it usually means I refactored my code and forgot to remove a type parameter somewhere. This sort of implicit phantom field would decrease productivity for me by introducing strange and hard-to-understand bugs.

We can introduce lints #![deny(unused_type_parameter)] which you can enable during refactoring.

And I suspect that most users of rust are not knowledgeable enough about type theory to know that a weird compiler message about variance really means "dude, you forgot to delete T from the list".

We should try to emit non-weird messages that are as natural as possible.

The main ergonomics cost, in my experience, is the annoyance of initializing _phantom fields everywhere the struct is created.

I think you are mostly right, but the different annoyances have a tendency to enhance each other. Initializing the phantom field is also a direct consequence of having a real field.

Probably my biggest concern: playing with dropck and variance sounds like a really dangerous game. Are we more likely to introduce unsoundness bugs into the type system by adding this?

No; a phantom T clause or field is functionally equivalent to PhantomData<T> wrt. variance, dropck, and auto-trait behavior - this can only lead to unsoundness if PhantomData<T> does. Of course there's always the possibility of bugs in the implementation; but this is true of all new features we add to the language.

Also, are we making it easier for users to write subtly unsafe code by making a variance error?

I don't see why... Unsafety flows from uses of unsafe - if you also have generics, you will have to think about variance regardless of this RFC or not. Folks who use unsafe should always consult the rustonomicon, today and tomorrow.

As I see it the biggest way to improve ergonomics would be to avoid having to initialize _phantom fields. To that end, maybe a much more conservative alternative would be to simply allow omission of initialization for zero-sized fields in struct literals?

This has been discussed previously in the RFC and in the alternatives. I don't think it is more conservative; but rather more radical. I think having this special behavior wrt. ZSTs is too special. I think the more consistent approach is using the Default trait.
@glaebhoerl

Presumably the fleshed-out version of the proposal would be more like "the compiler automatically inserts the literal to construct the ZST, and then checks that for errors normally", e.g. if the ZST had private fields you'd get a privacy error. (Inventing ZST values "out of thin air" would of course be a bad idea because it would violate the soundness of APIs.)

Just to make sure we're on the same page... This would be the behavior?
mod boundary {
  pub struct Foo {
    priv: (),
  }

  pub fn make_foo() -> Foo {
      Foo {} // OK; expanded to:  Foo { priv: () }
  }
}

pub fn make_foo() -> Foo {
  Foo {} // ERR! expands to: Foo { priv: () }   ==> privacy error.
}

This reminds me of another benefit of phantom T, that given:
struct Foo<X, Y>(X, phantom Y);
You can coerce Foo into a function pointer of type fn(X) -> Foo<X, Y> which can add further to ergonomics in some situations.

Note that this is also a property of #[always_default].  Given
struct Foo<X, Y>(X, #[always_default] Phantom<Y>);

The function added to the value namespace would just be
fn Foo<X,Y>(x: X) -> Foo<X, Y> { Foo { 0: x } }

Which itself would be desugared into
fn Foo<X,Y>(x: X) -> Foo<X, Y> { Foo { 0: x, 1: Default::default() } }

(#[derive] could also become aware of the attribute, and not show the field in Debug, or whatever.)

@Centril Yes.

If something like #[always_default] is added I think it should not actually call Default::default(). Silently inserting calls to arbitrary code that can have side effects is not a good idea. (Drop is the one exception as it’s one of the core concepts of the language.)
But then, is there a use case for #[always_default] other than phantom data?

I am also partial to "just allow omitting the fields" as a solution to the ergonomics problem. But as @SimonSapin points out, there is danger and apparently little value in doing this via something as general as Default. In fact I would propose doing this only for unit structs, which are always constructible without side effects in a way the compiler knows. This avoids special casing PhantomData further but is otherwise pretty minimal.

@Centril

I think this is mostly true of phantom clauses, but much less so for phantom fields.

"phantom fields" are still rather special unless you want to make phantom T a kind of type (which would have big repercussions). If it's not a type, then all that changes is the lexical position of the new widgets IIUC?

This is true on a superficial syntactic level, but not semantically.

I disagree. As I said, under this proposal:

A struct is no longer just generic parameters, where clauses and fields (and 2/3 of those are basically the same as on many other items), it now also has a fourth major component.

Generic parameters, where clauses and fields are not just grammar productions, they are also core concepts of the type system. Even "phantom fields" are rather different from normal fields because they are anonymous (tuple fields have a name, their index) and treated differently during some parts of type checking (e.g., struct literals). This impacts not just the parser, but also every treatment of the Rust type system (from the reference to the compiler).
To give one example, I'm reasonably sure that the compiler would get substantially more complex if it supported phantom clauses or fields in the obvious way. This is because phantom clauses are essentially a fourth new component to an ADT, and phantom fields don't neatly fit into the ADT abstraction either (e.g., fields always have names). If this RFC is accepted, it would probably be easier to internally keep PhantomData around and lower phantom clauses or fields to PhantomData fields (with some special casing for struct literals etc.).

So one solution that has been floated is to only make PhantomData a pure library type by defining it as pub struct PhantomData<T: ?Sized>; which works because the unused type parameter is seen as covariant, etc. What are your thoughts on this? I think that would keep most of the ergonomics benefits without introducing any surface syntax but rather removing a lang item.

It's an interesting idea, but I second the doubts about unsafe code others have mentioned. You mentioned multiple times that unsafety flows from unsafe blocks, not from type definitions, and that is technically true, but in practice people unsafe code authors don't write perfect code (if they did, Rust would not be any better for memory safety than C++). When a programmer writes an unsafe abstraction that requires using PhantomData in some way, they may forget to do so. In those cases it seems obviously better to error than to assume anything about the author's intent. For variance at least, invariance is a safer default than covariance, but that approach doesn't yield any answers for dropck and auto traits, so it's not obviously better than the error either.

For those that don't use phantoms to change variance, this will have zero impact on how Rust programs feel.

Again, having a first-class concept of phantom clauses/fields complicates things for everyone who reasons about the Rust type system. They already need to know about PhantomData, true, but that is a "detail" that you need to apply in certain special cases (when you actually go and compute the properties of a given type), not an entirely new entity as argued before.

We can introduce lints #![deny(unused_type_parameter)] which you can enable during refactoring.

I feel like this should be warn by default since I am imagine that in the vast majority of cases, it is actually an error.

I think you are mostly right, but the different annoyances have a tendency to enhance each other. Initializing the phantom field is also a direct consequence of having a real field.

I don't quite understand what you mean by "enhance" here. IMHO, if we didn't have to initialize them, 90% of the problem would be gone, and I can live with the remaining 10%. I guess this might just be a matter of opinion...

@rkruppe You make some good points in general; I agree that there are trade-offs and that the best path ahead is somewhat murky.

A struct is no longer just generic parameters, where clauses and fields (and 2/3 of those are basically the same as on many other items), it now also has a fourth major component.

My point here on semantics is that a struct already had the forth (or really six) major component if we call that component "variance + dropck + autotrait". I believe this is what you meant by "That's not quite true [..]".

[...] Even "phantom fields" are rather different from normal fields because they are anonymous (tuple fields have a name, their index) and treated differently during some parts of type checking (e.g., struct literals). [...]

Sure; you are right on the anonymity adding a 7th component to what a data type is.

If this RFC is accepted, it would probably be easier to internally keep PhantomData around and lower phantom clauses or fields to PhantomData fields (with some special casing for struct literals etc.).

Entirely possible - I wouldn't want to speculate myself ;)

When a programmer writes an unsafe abstraction that requires using PhantomData in some way, they may forget to do so. In those cases it seems obviously better to error than to assume anything about the author's intent. For variance at least, invariance is a safer default than covariance, but that approach doesn't yield any answers for dropck and auto traits, so it's not obviously better than the error either.

I think that if you are dealing with unsafe code, then that code should also be reviewed extensively and an informal "proof" should be provided as to why the abstraction is safe. I also think that a majority of cases where PhantomData<T> + unsafe are used together are similar to the case of Vec<T> where you've already used T but with the wrong variance. I expect that cases such as:
struct Id<A, B>(PhantomData<(fn(A) -> A, fn(B) -> B)>);

are quite rare. In any case, it is easy to get variance wrong even when you are forced to use PhantomData which actually happened in the case of Id<A, B>. The only reliable protection against unsoundness here is to teach people that when you mix type constructors and unsafe, then you should always think about variance (which the rustonomicon does).
I also don't like the idea of penalizing safe code due to marginal risks wrt. unsafe code.

They already need to know about PhantomData, true, but that is a "detail" that you need to apply in certain special cases [..]

Certainly. However, I'd argue that the prominence is warranted given how fundamental variance is.
@mark-i-m

I feel like this should be warn by default since I am imagine that in the vast majority of cases, it is actually an error.

I suspect you would notice an entirely unconstrained type variable quickly due to type inference having no anchor at all - and so you'd get a "type not known" error.. If we opt for warn-by-default, this will imo negate most of the ergonomics benefits of allowing unused parameters and at that point it all becomes a bit "Meh..".

I don't quite understand what you mean by "enhance" here.

That an annoyance are not the sum of its parts and that adding more annoyances does not scale linearly with the number of annoyances. I certainly agree that allowing users to elide the import and the initialization is a good and smaller (in terms of the diff) alternative.

"Variance by example" is definitely the best. I remember when rust switched from variance markers to phantom data, and it made things so much simpler for me (However, I find Java's syntax understandable too. I just put off by the PLT terms).
The phantom name itself isn't that great though. It only describes what it is, but not what it's for. The alternative with variance_clause_keyword/owns looks much much better IMHO.
I'd be cautious about interpreting unused type parameters without a warning. As a noob I used to place them in random places without having much clue what I'm doing. I'm fine with a "if that's really what you mean, put a keyword on it" error.
I've got a feeling that this RFC focuses mainly on a pseudo-field only because historically in Rust this had to be a field. To me this feature makes more sense as a where-like annotation.

@kornelski

The phantom name itself isn't that great though. It only describes what it is, but not what it's for. The alternative with variance_clause_keyword/owns looks much much better IMHO. I'd add indirectly to the potential vocabulary for this, since it's used for structs that actually own T, but in non-obvious ways.

The problem I have with owns is that it might wrongly give the impression that this is about memory rather than just a logical "you can reach a T from this type".

I've got a feeling that this RFC focuses mainly on a pseudo-field only because historically in Rust this had to be a field. To me this feature makes more sense as a where-like annotation.

At first, I wasn't going to include phantom fields at all and only go with allowing the user to elide unused parameters; I'm totally fine with changing the RFC to where-like annotations if we feel that's the best way forward ;)

Hello
While I agree with the viewpoint that the problem doesn't warrant a new keyword and syntax change (there's 605 occurrences of PhantomData in whatever role, including comments, in Rust source code, compared to eg 84582 of Vec ‒ seems like rare enough), I think I have another view to the problematic.
I have little knowledge about type theory. I survived coding in Haskell without knowing exactly what a monad is (I have a certain feeling what they are good for, but I couldn't define it exactly), and the only place I've ever seen mention variance was the Nomicon. I did write bits of unsafe code and I did use PhantomData on occasion or two.
For me, the PhantomData<T> field very closely reflects my mental model. Just like Vec<T> is a container and it means some Ts live inside, PhantomData<T> is some kind of a weird container that pretends to hold Ts, for the cases when the rest of the structure really holds them, but rustc can't see them. It just feels easier to grasp without introducing any new principles into the thinking and mental model. The fact I have to import the type and initialize the field even reintroduces the model of a „container“ of sorts ‒ sure, it's a bit more code, but makes thinking about it easier. And yes, I know this „container“ is somewhat magical inside, but I guess I can trust the compiler to do what's necessary about it (eg. encapsulation of magic).
I understand there are people who deal with these kinds of things more often than me, and making it a bit more ergonomic might be good for them. But I think there are many more people like me, that use it once in a blue moon. I'd still have to adapt to the new way and learn what it means (which I could, but it would incur additional mental exercise) for that occasional use. And I don't think it's just about the change ‒ from the naïve point of view, PhantomData acts similar to other things, new keyword doesn't.
So I guess I kind of challenge the claim the phantom T way is more convenient or ergonomic. It doesn't feel that way to me, an occasional user. Less characters typed doesn't necessarily mean more ergonomic.
(Oh, and by the way, coming from the C++ world, I'm somewhat wary about spraying too many keywords and concepts around  ‒ oftentimes, I see a method declaration where more space is taken by various keywords and modifiers than the actual name, parameters and return values ‒ which is scary).

IMHO if any clause were added, it should be collapsed by default in the rustdoc, sort of like how attributes are now.
I think that one of the biggest problems with Rust right now is that people treat it like they do with C: people are so used to just telling the compiler "shut up, I know what I'm doing" that when Rust fights back and says "no, you actually can't do that," people get rather upset and feel like the compiler is just being picky, rather than feeling like they are actually at fault.
For example, when I first started Rust, 90% of the errors I got from the compiler were confusing messages about the borrow checker that I didn't really understand. Now, I occasionally get these but barely remember I get them, because I know what they mean and know how to fix them.
PhantomData is one of the few cases in Rust when I feel less like the compiler is doing something clever, and more like I'm pushing the compiler away and saying "I know what I'm doing." While people who understand unsafe Rust see it as something that is very specific and simply a part of designing a type, most people who encounter it are probably using it because they spend 5 seconds searching for their problem and found an example that used PhantomData.
IMHO, having struct RawVec<T> owns T makes it crystal-clear what's going on, much more than hiding away a ghastly T inside the struct itself. Teaching that adding a field: T implicitly adds an owns T, but not the other way around, lets the compiler know exactly what is inside a type and how it should respond.
This feels a lot closer to explicitly specifying a lifetime instead of eliding it, rather than the existing solution which feels like instead flipping a table and using raw pointers instead.

I think that one of the biggest problems with Rust right now is that people treat it like they do with C: people are so used to just telling the compiler "shut up, I know what I'm doing" that when Rust fights back and says "no, you actually can't do that," people get rather upset and feel like the compiler is just being picky, rather than feeling like they are actually at fault.

👍  proposed  the quote of the week.

I don't have a strong opinion about the introduction of new syntax for phantom T. However, I'm concerned about disabling the warning for unused type parameters. In the history of receiving that warning on my own code, for me it has almost always meant "you forgot to make the code match the type parameters" or "you deleted some code and need to adjust the type parameters" or "you wrote the type parameters you thought you needed and forgot to update them when it turned out you didn't", and almost never meant "you need a PhantomData".
I'd like that warning to remain on by default, rather than implicitly treating such unused type parameters as PhantomData.

@joshtriplett So perhaps a middle ground re "warning by default" would be:
#[allow(implicit_phantom)] // bikeshed on lint name...

#[allow(unused_generic_parameter)] seems fine to be. Basically turning the current error into a lint warning. Sadly we also need to change variance inference, so the implementation is not that easy.

@Centril There's been a lot of discussion, and I've accepted that "generic" vs (runtime) "value" and "parameter" vs "argument" are orthogonal (the latter two also known as "formal" vs "actual").

An alternative alternative: trait Stateless.
Background
My initial take on this RFC was that the most important motivation for it is (3.) users must "acknowledge the field by adding an expression PhantomData when a value of the type is to be constructed."
Skimming over the comments here, I saw a number of people say we should consider addressing that problem on its own; if we did that, then there would not be too much motivation for the other changes proposed here. (That matches my own attitude at the moment.)
Here are some of the points in the comment thread discussing this:

leodasvacas, March 5: https://github.com/rust-lang/rfcs/pull/2357#issuecomment-370423460
SimonSapin, March 5: https://github.com/rust-lang/rfcs/pull/2357#issuecomment-370434462
glaebhoerl, March 10: https://github.com/rust-lang/rfcs/pull/2357#issuecomment-372048009
mark-i-m, March 10: https://github.com/rust-lang/rfcs/pull/2357#issuecomment-372055231
rkruppe, March 11: https://github.com/rust-lang/rfcs/pull/2357#issuecomment-372142870

Anyway, the RFC currently notes this via the alternative "Allow filling unspecified fields with Default."

However, that option is problematic for a number of reasons (given in the RFC and in the comment thread).

Proposal
I here suggest this more conservative alternative to that alternative.
We add builtin marker trait: trait Stateless: Copy { }.

Types that implement Stateless must be Zero-Sized Types (ZSTs), which is checked by the compiler.
One can #[derive(Stateless)] struct Type { ... } or do impl Stateless for Type { }, just like other builtin traits like Copy.
When writing an expression to construct an instance of an ADT (i.e. struct or enum), one can omit any fields of a type that implement Stateless.
When writing a pattern to deconstruct an instance of an ADT, one can likewise omit any fields that implement Stateless
.... And, we will (of course) have PhantomData implement Stateless.

To me, that seems like enough to accomplish the goal of addressing the ergnomic issue (3.), while not falling into the global_lock::Locked problem noted in the RFC, because now one must opt into allowing a type to be omitted from constructions and deconstructions.

So: Has this been proposed before? I know it doesn't address all the points noted in the RFC, but there seems to be significant debate about whether all of those points are things we necessarily want to fix...