-
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: Hidden trait implementations #2529
Conversation
โฆteaching, motivation.
How does this interact with blanket impls? Like suppose it were Edit: A better description: for a concrete |
The interaction is described here. While this may seem weird, I think it is also useful in some cases. Consider a conditional and hidden blanket impl like However, I don't think this sort of intentional prevention will be the main use for this. It is something that you can do, but the main use case is to allow users to implement traits they actually need to use for internal purposes but to not expose implementations and avoid guaranteeing them.
That depends on what you mean by see here. The error message in the link above is: error[E0119]: conflicting implementations of trait `crate_A::Property` for type `Thing`:
--> src/main.rs:<line>:<column>
|
L | impl Property<Thing> for Thing {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: conflicting implementation in crate `A`:
- crate impl<T> crate_A::Property<T> for T { ... }
note: the implementation exists, but is hidden. Note in particular that the author of crate B can see that there is a hidden implementation in crate A that is in the way. This enables B's author to contact A's author and see if they can perhaps work something out. As a final note, the reason why E0119 must be emitted is that we must preserve coherence because to violate coherence would be unsound and cause breaking changes to unsafe code which may assume coherence as an invariant. The canonical example of this is the hash table problem. |
I think this RFC needs a section about how it interacts with specialisation, consider this: // crate A
trait Property {
fn get(&self) -> u32;
}
impl<T> Property for T {
default fn get(&self) -> u32 { 42 }
}
crate impl Property for Thing {
fn get(&self) -> u32 { 4242 }
} Crate |
@TimNN Nicely spotted! I hadn't considered that interaction, so thank you for raising this. :)
If by "see" we mean "use", then if this were allowed to happen, then it would cause the type system to be unsound because it would be incoherent (i.e: allow different dynamic semantics depending on where the implementation was resolved in). So I see two principal ways to deal with this:
What do you think? |
(2) would be my preference as well (also because IMO it's simpler to understand than option (1)). |
@TimNN Yeah that's a great point! In fact, it felt heavier to write the explanation for 1) so I wrote the one for 2) first =P. The explanation for 2) is also more obvious I think since you can fit it into an error message. |
I'm kinda dubious about the motivation here: Are we trying to expose I suspect this would usually be best addressed with hidden types and simplified delegation, but given that delegation has languished so long.. I'm wondering if some lint scheme might achieve this better, so the impls would technically be exposed, but any user outside the current crate gets a warning specified at trait declaration time.
I'm thinking lints prevent crate authors from really depend on hiding impls, so they'll be more conservative, while simultaneously admitting a wider range of warnings:
It just seems like hiding is too blunt an instrument and people who want this should explain when the impl is or is not usable. A lint could permit hiding generic impls that exist for whatever reason, but would probably not hiding anything automatically, instead the hider should write out lints for each problematic trait. |
In the case of
Other examples, as discussed in the motivation, include not wanting to expose
This is discussed in depth in the section on alternatives. Remember that you both have to 1) define the newtype struct, 2) wrap (and sometimes unwrap) the base type in the newtype every time you need the crate-local-only implementations. 3) create the delegating implementations. Thus, comparatively speaking, using forms of GeneralizedNewtypeDeriving or delegation per #2393 requires a considerably larger, relatively speaking, annotation burden as compared to just adding Also consider that delegation or newtype-deriving does not generally work and can be quite restrictive. For example, if we take a look at RFC #2393, it does not work at the moment for: associated types, associated consts, All in all, I believe using the newtype approach is an indirect approach that does not highlight the author's intent and that adds a non-trivial amount of extra thinking and reading burden on the author. Thus, I think it is a worse solution for readability, maintainability, and for writing ergonomics.
I will add a discussion about this in the section on alternatives. However, when dealing with Furthermore, it is not clear how this I would also like to add that comparatively speaking, As for wanting to provide more tailored error messages, we could expose better facilities for custom error messages in general, and hidden implementations could be part of that. For example, you might have some sort of However, I think that most of the time, you would just like to hide an implementation without explaining yourself. In the cases where I have needed this, that applied. If I had to provide an explanation, there is a chance that it would have been too heavy a burden wherefore I might have accidentally publicized the implementation instead. |
It sounds like visibility modifiers for impls might not adhere to the same rules as visibility modifiers for inherent items or non-public traits though? Are you sure it's wise to permit hiding all traits? Can I hide only a supertrait? It sounds strange to hide How does hiding interact with generic impls? If I hide It's tempting to keep hidden impls in line with existing hidden traits here. That results in incomplete hiding, but incomplete hiding is what you want at least half the time anyways, and incomplete hiding is at least fixable with more visibility modifiers, ala specialization. Can you forbid hiding an impl for your own tait via syntaxes enabled by specialization, amybe I do think lints provide answers to most of these questions, namely the burden is on the hider for both coverage and information. It's impossible for these answers to always be correct though because the space is more complex than with existing visibility modifiers. |
In what sense? The differences where they exist are specified in the reference except for the interaction with specialization which I've proposed a rule for in the above discussion.
Rules for auto traits are specified in point 8. pub(self) impl !Send for MyUnsafeType {} As for Can you elaborate on
No. This is specified in 4. #[derive(crate Clone, pub Copy)]
struct Foo; but you may write: #[derive(pub Clone, crate Copy)]
// equivalently:
#[derive(Clone, crate Copy)]
struct Foo;
You can't hide a bound. You can hide the fact that some type
If If you believe there are faults in the technical specification of this RFC, I invite you to leave line comments where appropriate so that I may fix those faults.
Elaborate? I have discussed the situation wrt. blanket impls here: #2529 (comment).
I don't believe in the linting approach because fundamentally, lints are not about encapsulation and API abstraction. We use visibility modifiers for that to enforce properties, so it seems to me natural to use |
I'd missed that hidden bounds were forbidden. That makes this less complex and much less useful, which sounds good. I do still think hidden impls should always be justified in comments. As otherwise downstream could reasonably just fork the upstream under the assumption that upstream was being overly shy about its public API. Clippy could maybe complain if a hidden impl lacked a comment? |
Opposed. Case it addresses is not common enough to warrant extension, cost is nontrivial, risk of mistaking it for modular instances is high, and existing pattern of using private newtypes is sufficient and not too burdensome in the rare cases this occurs. |
imo One of the use cases is very common but possibly regularly overlooked in crate API design (and buried in the RFC among talk of coherence / newtype). From the RFC
The case I'm concerned about I think is the same that happened for regex. When creating an
Personally, I'm not seeing it as something that would be mistaken.
I can't speak to cost or whether this causes any soundness issues. If this is possible, I think it would be a big help to reduce overhead for API design best practices (currently more trivial to implement |
Unfortunately, the current state of this proposal does when combined with specialization (see #2529 (comment), #2529 (comment)). For a possible resolution of the soundness issue see: #2529 (comment). Since this is rather subtle and not clear whether this is sufficiently scrutable for users, I have been putting of further work on this RFC for now in favor of more pressing concerns. As for cost, @nikomatsakis would know more; I think Chalk should make this easier. |
Where I very much want something like this is for implementing traits that mention types from private dependencies. If you'll note, all the concrete examples of use of this potential feature fall into this case. If we restrict it to this case, standard orphan rules should cover all potential problems with hiding the implementation, so it wouldn't have to "leak" into error messages. I agree that the "full" scoped implementation is problematic. But I think crate-local implementation of traits mentioning foreign types is a less problematic subset. (EDIT: specialization and blanket implementation make this not perfectly true. I still think that the "private dependency" case should always be sound, though, because the implementation can always be added later, right? Specialization is a hard problem, and I haven't really kept up with that nor its relevance to this.) Are you in opposition to just that subset, @graydon? It could be accessible via a broader public/private dependency split (which would otherwise have to forbid said implementations entirely) or more granularly (to allow use with public dependency types). At the very least, I don't think |
@CAD97 not sure, that's a different proposal than what's on the table here. I like minimizing accidental exports and global leakage (indeed it's why I argued against having typeclasses initially) but the ship sailed: reasoning about the impl relation is global. Starting to invent secondary ways of making it non-global in addition to the visibility rules around the traits and types involved in the relation seems to me like inviting more confusion than you could gain from avoid from the cases the existing system is misused. |
I'm unsure if this has been brought up, seeing as I don't have time to read the entire thread, but it'd also be great if it were considered to allow using this to implement external traits for external types; I can't count the amount of times I've wanted to do this for a binary I've been doing which I then need to pollute with wrapper types (this especially happens with |
Apologies if this has already come up, but type privacy (described by RFC 2145) contributes a notion of implementation visibility that can be used to achieve scoped trait impls. The semantics of this pattern might provide a useful, minimal foundation on which to build a |
related #493 |
We discussed this in the backlog bonanza meeting today. We're definitely interested in something like this eventually -- the safe transmute work would like it, for example -- but this is currently the kind of big idea that's not a good fit for the current roadmap cycle, and implementation-wise would probably end up waiting until the chalk work is integrated anyway. @rfcbot fcp postpone |
Team member @scottmcm has proposed to postpone 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 at most 2 approvals are outstanding), 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. |
๐ This is now entering its final comment period, as per the review above. ๐ |
The final comment period, with a disposition to postpone, as per the review above, is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. The RFC is now postponed. |
๐ผ๏ธ Rendered
๐ Summary
Allow a visibility modifier on a
trait
implementation such that:An example:
๐ Thanks
To @aturon, @Mark-Simulacrum, and @kennytm for reviewing the draft version of this RFC.