-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Ghost Busting #2357
RFC: Ghost Busting #2357
Conversation
text/0000-ghost-busting.md
Outdated
While `phantom` fields are not nameable, they permit visibility modifiers on | ||
them. The default visibility of a `phantom` field is private as with other | ||
fields. Therefore, having a type definition with no fields but private `phantom` | ||
fields will cause other modules to not being able to construct the type. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not be able*
constructed unless the "field" is visible in the module in question. | ||
|
||
This property is crucial because if `phantom` fields always were public, then | ||
the a value of type `Id<A, B>` where `A != B` could be constructed with `Id {}` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This specific instance could (and should) be fixed using the non_exhaustive attribute. Do you have a better use case for privacy in phantom fields?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation of #[non_exhaustive]
says:
Structs marked as non_exhaustive will not be able to be created normally outside of the defining crate.
Essentially, this is the same as pub(crate)
.
Allowing for more fine grained visibility as pub(super)
is more general and lets you control visibility on level not permitted by #[non_exhaustive]
. I also believe that pub(crate)
is a lot more clear than #[non_exhaustive]
wrt. intent and what it means. I think that #[non_exhaustive]
should be used for when you actually intend to add more fields or are unsure about that while visibility (pub, ..
) should be used for controlling who can see what.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not like you couldn't work around it by writing
struct Id<A, B> { phantom A, phantom B, _priv: () }
It really makes no sense for phantom fields to be subject to visibility checks. You can always add a unit field, and if that's not acceptable, then the problem can be fixed in a more general way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea of using a _priv field is discussed in the alternatives section. You say that there is no reason for phantom fields to be subject to visibility, but a _priv field will need to be passed in struct literals, making things less ergonomic, so there are reasons to make phantom fields subject to visibility. It also increases uniformity of fields to do so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems to me that phantom ()
is a more natural way to force a private field, than requiring it be named anyway.
text/0000-ghost-busting.md
Outdated
Respecting privacy also allows us to encode the [following idiom][screeps_api]: | ||
|
||
```rust | ||
pub struct Thing { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also should be using the non_exhaustive attribute.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm yes, in this case I'm inclined to agree... However on a second look, the example was just wrong and didn't add anything, so I just removed it =)
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 In particular, I'm not convinced that
This one's more interesting to me, but I think it's largely applicable to many ZSTs, not just
Wouldn't your #2353 handle this just fine? And I think it needs a more realistic example than |
For all practical intents and purposes, yes.
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.
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.
Can you expand on these?
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 What I like about unused parameters => |
and covariant. | ||
|
||
2. Introduces `phantom T` pseudo-fields which improves the ergonomics of | ||
changing [variance] and [drop checking] behavior when it is needed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And auto trait implementations?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Elaborate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PhantomData<T>
only implements auto traits that T
does. This is used in fmt to opt out of all auto traits.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Today I learned... Interesting. I'll write something about it when I'm more lucid =P
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated the RFC accordingly now.
I think that "add a pseudo-field using
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.
|
text/0000-ghost-busting.md
Outdated
|
||
```rust | ||
struct MyVec<T> { | ||
phantom T, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is writing phantom T
here optional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, phantom T
is required to let dropck know that T
is logically owned, which is not the case if it only sees data: *const T
. I will clarify.
|
||
```rust | ||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] | ||
struct Label<T>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here it seems phantom T
is indeed optional.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, that's right.
@scottmcm Ah yes, (2) only goes away morally when you don't have to acknowledge unused type parameters.
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 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
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
Seems like a variation on
I think this was @eddyb's idea? This would be: |
I see two possible intents of a user that gets the unused type parameter error:
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 |
@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 |
+1 to defaults for unused type parameters (while keeping -1 to the new special syntax and phantom fields sometimes behaving as normal fields and sometimes not. |
@petrochenkov Might perhaps be better to make
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"? |
Privacy treatment, for example ( |
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 |
I agree with @scottmcm that I don't feel like specific syntax is required to replace 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 |
I don't agree that phantoms are niche; I find that they show up plenty when using a little more advanced type features.
And
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 |
I am very very sympathetic with removing the need for declaring a Take euclid for instance: I would love for people to be able to write 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 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 |
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 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 ( I'd argue that the mental cost is also quite small or even reduced compared to |
@quininer, @fstirlitz, @tanriol, can you elaborate on your concerns? @kennytm, what is confusing you? can I elaborate on something? |
This RFC is mostly about syntax sugar. I think Rust should have more room to experiment with various syntaxes by supporting "transpilers" like JavaScript's Babel. This way users could try out, in real-world code, alongside other syntax changes, whether it makes sense as a https://internals.rust-lang.org/t/pre-rfc-first-class-support-for-compile-to-rust-languages/7610 |
Recently, I've written some code that uses phantom data more than the usual amount, and it actually was the „You need PhantomData“ case. And considering the experience, I'd say I'm against defaulting to In other words, at least half of the time I didn't end up using |
Based on my experience I think I'm inclined to agree. |
This has been open for a long time,and I feel like it doesn't have sufficient justification. There's a solution for this problem already, it's documented and understood, and I don't think the proposed syntax is a sufficient improvement to justify expanding the surface area of the language. Thus: @rfcbot close |
Team member @joshtriplett has proposed to close this. The next step is review by the rest of the tagged team members: No concerns currently listed. Once a majority of reviewers approve (and none object), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
@pnkfelix I've seen talk of similar things before (I've called something similar |
Default fields don't improve destructuring. |
I'd be in favor of a much smaller RFC that just made the variance implied by
However the |
Actually on second thought rather than getting rid of the error entirely, I'm in favor of downgrading it to a warning, using the default variance rules if the type is omitted. That way you still get nagged if, once you've finished filling out your code, you still haven't used that type parameter (encouraging you to use PhantomData then if its really necessary), but mid-mock you just get another dead code warning that you can ignore, as you are probably already doing. Anyway that's a different RFC from this one. |
(@withoutboats I like the idea of a warning, which seems like a good resolution; just w.r.t. previous comment, note @vorner's comment above, according to which |
@glaebhoerl I read @vorner's comment, I think @vorner's experience is squarely in the very small space excluded by "nearly always." But I think reducing unused type parameters to a warning would suffice for everyone. |
On Sun, Jan 20, 2019 at 03:04:26PM -0800, boats wrote:
Actually on second thought rather than getting rid of the error entirely, I'm in favor of downgrading it to a *warning*, using the default variance rules if the type is omitted. That way you still get nagged if, once you've finished filling out your code, you still haven't used that type parameter (encouraging you to use PhantomData *then* if its really necessary), but mid-mock you just get another dead code warning that you can ignore, as you are probably already doing.
I'm not a fan of the idea of turning this error into a warning or
dropping it; I'd like it to remain an error.
Anyway that's a different RFC from this one.
True.
|
What about a deny by default lint? |
I see basically no value in that.
…On Tue., Jan. 22, 2019, 00:19 Who? Me?! ***@***.*** wrote:
What about a deny by default lint?
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#2357 (comment)>, or mute
the thread
</~https://github.com/notifications/unsubscribe-auth/AT4HVaS2wYZceD_UTDYHVwKfpTiAE9oOks5vFfa4gaJpZM4SbPbp>
.
|
🔔 This is now entering its final comment period, as per the review above. 🔔 |
I feel like it'd be useful to enumerate some of the scenarios where phantom data is annoying. One use case for me is kind of "typed indices" (I think the pattern is more general, but I can't think of a good summary term, so I'll leave it at that). I have been annoying a number of times because I have structs that are like struct Index<T> { .. } where the fn get<T>(x: impl Index<T>) -> T In these cases, I basically always want invariant, and I... probably don't really want to consider (Honestly, I feel like what I want here is sort of an "associated type" for a struct, e.g., I also agree with @withoutboats that refactoring is annoying. |
Another scenario where phantom data is annoying (somewhat similar to @nikomatsakis's type WorldPoint2D = TypedPointe3D<f32, WorldSpace>;
type ScreenPoint2D = TypedPoint3D<f32, ScreenSpace>;
type Projection = TypedTransform3D<f32, WorldSpace, ScreenSpace>; TypedPoint3D source code: /~https://github.com/servo/euclid/blob/1244c867a8a7bcd4333c6555ea1f7305abf997d9/src/point.rs#L417 It'd be great if euclid could let you write: // No can do, gotta use WorldVector3D::new(1.0, 2.0, 3.0)
let v: WorldVector3D = Vector3D { x: 1.0, y: 2.0, z: 3.0 }; However at the moment the language enforces the My other main exposure to phantom types is the sid crate which corresponds to @nikomatsakis's In both cases the phantom type is only there as a some sort of tag and properties of the actual type |
A priori, there is no subtyping relationship among unit types, so you would not expect the variance to matter. In fact, any normal data would be covariant because someone might define traits As an aside, I just learned that no variance annotation in a C# interface denotes invariance, which does not exist ion Rust, but still might confuse someone. Anyway there is nothing wrong with defining some |
The final comment period, with a disposition to close, as per the review above, is now complete. By the power vested in me by Rust, I hereby close this RFC. |
🖼️ 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 changingvariance and drop checking behavior when it is needed. A
phantom T
field also hasthe same behavior with respect to auto traits as
PhantomData<T>
does.PhantomData<T>
is redefined asstruct 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
impl
s.That's it folks; Happy 👻👤!
💖 Thanks
Thanks to @nikomatsakis and @eddyb for interesting discussions.