44
55# Summary
66
7- Make it illegal to expose private items in public APIs.
7+ Require a feature gate to expose private items in public APIs, until we grow the
8+ appropriate language features to be able to remove the feature gate and forbid
9+ it entirely.
810
911
1012# Motivation
1113
12- Seeing private items in the types of public items is weird.
14+ Only those language features should be a part of 1.0 which we've intentionally
15+ committed to supporting, and to preserving their behavior indefinitely.
1316
14- It leads to various "what if" scenarios we need to think about and deal with,
15- and it's easier to avoid them altogether.
17+ The ability to mention private types in public APIs, and the apparent usefulness
18+ of this ability for various purposes, is something that happened and was
19+ discovered by accident. It also gives rise to various bizarre
20+ [ questions] [ questions ] and interactions which we would need to expend effort
21+ thinking about and answering, and which it would be much preferable to avoid
22+ having to do entirely.
1623
17- It's the safe choice for 1.0, because we can liberalize things later if we
18- choose to, but not make them more restrictive.
24+ The only intentionally designed mechanism for representation hiding in the
25+ current language is private fields in ` struct ` s (with newtypes as a specific
26+ case), and this is the mechanism which should be used for it.
1927
20- If I see an item mentioned in rust-doc generated documentation, I should be
21- able to click it to see its documentation in turn.
28+ [ questions ] : https://github.com/rust-lang/rust/issues/10573
2229
23- ## Examples
30+
31+ ## Examples of strangeness
2432
2533(See also https://github.com/rust-lang/rust/issues/10573 .)
2634
@@ -35,22 +43,184 @@ able to click it to see its documentation in turn.
3543 it doesn't make any sense.
3644
3745 * Can I access public fields of a private type? For instance:
38-
46+
3947 struct Foo { pub x: int, ... }
4048 pub fn foo() -> Foo { ... }
4149
4250 Can I now write ` foo().x ` , even though the struct ` Foo ` itself is not visible
43- to me, and in rust-doc, I couldn't see its documentation? In other words,
51+ to me, and in ` rust-doc ` , I couldn't see its documentation? In other words,
4452 when the only way I could learn about the field ` x ` is by looking at the
45- source code for the module?
53+ source code for the given module?
4654
47- * Can I call methods on a private type? Can I "know about" its trait ` impl ` s?
55+ * Can I call public methods on a private type?
56+
57+ * Can I "know about" ` trait ` s the private type ` impl ` s?
4858
4959Again, it's surely possible to formulate rules such that answers to these
50- questions can be deduced from them mechanically. But even thinking about it
51- gives me the creeps. If the results arrived at are absurd, then our assumptions
52- should be revisited. In these cases, it would be cleaner to simply say,
53- "don't do that".
60+ questions can be deduced from them mechanically. But that doesn't mean it's a
61+ good idea to do so. If the results are bizarre, then our assumptions should be
62+ reconsidered. In these cases, it would be wiser to simply say, "don't do that".
63+
64+
65+ ## Properties
66+
67+ By restricting public APIs to only mentioning public items, we can guarantee that:
68+
69+ * Only public definitions are reachable through the public surface area of an API.*
70+
71+ Or in other words: for any item I see mentioned in ` rust-doc ` generated
72+ documentation, I can * always* click it to see * its* documentation, in turn.
73+
74+ Or, dually:
75+
76+ * The presence or absence of private definitions should not be observable or
77+ discoverable through the public API.*
78+
79+ As @aturon put it:
80+
81+ > One concrete problem with allowing private items to leak is that you lose some
82+ > local reasoning. You might expect that if an item is marked private, you can
83+ > refactor at will without breaking clients. But with leakage, you can't make
84+ > this determination based on the item alone: you have to look at the entire API
85+ > to spot leakages (or, I guess, have the lint do so for you). Perhaps not a
86+ > huge deal in practice, but worrying nonetheless.
87+
88+
89+ ## Use cases for exposing private items, and preferable solutions
90+
91+ ### Abstract types
92+
93+ One may wish to use a private type in a public API to hide its implementation,
94+ either by using the private type in the API directly, or by defining a
95+ ` pub type ` synonym for it.
96+
97+ The correct solution in this case is to use a newtype instead. However, this can
98+ currently be an unacceptably heavyweight solution in some cases, because one
99+ must manually write all of the trait ` impl ` s to forward from the newtype to the
100+ old type. This should be resolved by adding a [ newtype deriving feature] [ gntd ]
101+ along the same lines as GHC (based on the same infrastructure as
102+ [ ` Transmute ` ] [ 91 ] , née ` Coercible ` ), or possibly with first-class module-scoped
103+ existential types a la ML.
104+
105+ [ gntd ] : https://www.haskell.org/ghc/docs/7.8.1/html/users_guide/deriving.html#newtype-deriving
106+ [ 91 ] : https://github.com/rust-lang/rfcs/pull/91
107+
108+
109+ ### Private supertraits
110+
111+ A use case for private supertraits currently is to prevent outside modules from
112+ implementing the given trait, and potentially to have a private interface for
113+ the given types which is accessible only from within the given module. For
114+ example:
115+
116+ trait PrivateInterface {
117+ fn internal_id(&self) -> uint;
118+ }
119+
120+ pub trait PublicInterface: PrivateInterface {
121+ fn name(&self) -> String;
122+ ...
123+ }
124+
125+ pub struct Foo { ... }
126+ pub struct Bar { ... }
127+
128+ impl PrivateInterface for Foo { ... }
129+ impl PublicInterface for Foo { ... }
130+ impl PrivateInterface for Bar { ... }
131+ impl PublicInterface for Bar { ... }
132+
133+ pub fn do_thing_with<T: PublicInterface>(x: &T) {
134+ // PublicInterface implies PrivateInterface!
135+ let id = x.internal_id();
136+ ...
137+ }
138+
139+ Here ` PublicInterface ` may only be implemented by us, because it requires
140+ ` PrivateInterface ` as a supertrait, which is not exported outside the module.
141+ Thus ` PublicInterface ` is only implemented by a closed set of types which we
142+ specify. Public functions may require ` PublicInterface ` to be generic over this
143+ closed set of types, and in their implementations, they may also use the methods
144+ of the private ` PrivateInterface ` supertrait.
145+
146+ The better solution for this use case, which doesn't require exposing
147+ a ` PrivateInterface ` in the public-facing parts of the API, would be to have
148+ private trait methods. This can be seen by considering the analogy of ` trait ` s
149+ as generic ` struct ` s and ` impl ` s as ` static ` instances of those ` struct ` s (with
150+ the compiler selecting the appropriate instance based on type inference).
151+ Supertraits can also be modelled as additional fields.
152+
153+ For example:
154+
155+ pub trait Eq {
156+ fn eq(&self, other: &Self) -> bool;
157+ fn ne(&self, other: &Self) -> bool;
158+ }
159+
160+ impl Eq for Foo {
161+ fn eq(&self, other: &Foo) -> bool { /* def eq */ }
162+ fn ne(&self, other: &Foo) -> bool { /* def ne */ }
163+ }
164+
165+ This corresponds to:
166+
167+ pub struct Eq<Self> {
168+ pub eq: fn(&Self, &Self) -> bool,
169+ pub ne: fn(&Self, &Self) -> bool
170+ }
171+
172+ pub static EQ_FOR_FOO: Eq<Foo> = {
173+ eq: |&this, &other| { /* def eq */ },
174+ ne: |&this, &other| { /* def ne */ }
175+ };
176+
177+ Now if we consider the private supertrait example from above, that becomes:
178+
179+ struct PrivateInterface<Self> {
180+ pub internal_id: fn(&Self) -> uint
181+ }
182+
183+ pub struct PublicInterface<Self> {
184+ pub super0: PrivateInterface<Self>,
185+ pub name: fn(&Self) -> String
186+ };
187+
188+ We can see that this solution is analogous to the same kind of
189+ private-types-in-public-APIs situation which we want to forbid. And it sheds
190+ light on a hairy question which had been laying hidden beneath the surface:
191+ outside modules can't see ` PrivateInterface ` , but can they see ` internal_id ` ?
192+ We had been assuming "no", because that was convenient, but rigorously thinking
193+ it through, ` trait ` methods are conceptually public, so this wouldn't
194+ * necessarily* be the "correct" answer.
195+
196+ The * right* solution here is the same as for ` struct ` s: private fields, or
197+ correspondingly, private methods. In other words, if we were working with
198+ ` struct ` s and ` static ` s directly, we would write:
199+
200+ pub struct PublicInterface<Self> {
201+ pub name: fn(&Self) -> String,
202+ internal_id: fn(&Self) -> uint
203+ }
204+
205+ so the public data is public and the private data is private, no mucking around
206+ with the visibility of their * types* . Correspondingly, we would like to write
207+ something like:
208+
209+ pub trait PublicInterface {
210+ fn name(&self) -> String;
211+ priv fn internal_id(&self) -> uint;
212+ }
213+
214+ (Note that this is ** not** a suggestion for particular syntax.)
215+
216+ If we can write this, everything we want falls out straightforwardly.
217+ ` internal_id ` is only visible inside the given module, and outside modules can't
218+ access it. Furthermore, just as you can't construct a (` static ` or otherwise)
219+ instance of a ` struct ` if it has inaccessible private fields, you also can't
220+ construct an ` impl ` of a ` trait ` if it has inaccessible private methods.
221+
222+ So private supertraits should also be put behind a feature gate, like everything
223+ else, until we figure out how to add private ` trait ` methods.
54224
55225
56226# Detailed design
@@ -60,17 +230,20 @@ should be revisited. In these cases, it would be cleaner to simply say,
60230The general idea is that:
61231
62232 * If an item is publicly exposed by a module ` module ` , items referred to in
63- the public-facing parts of that item (e.g. its type) must themselves be
233+ the public-facing parts of that item (e.g. its type) must themselves be
64234 public.
65235
66- * An item referred to in ` module ` is considered to be public if it is visible
236+ * An item referred to in ` module ` is considered to be public if it is visible
67237 to clients of ` module ` .
68238
69239Details follow.
70240
71241
72242## The rules
73243
244+ These rules apply as long as the feature gate is not enabled. After the feature
245+ gate has been removed, they will apply always.
246+
74247An item is considered to be publicly exposed by a module if it is declared ` pub `
75248by that module, or if it is re-exported using ` pub use ` by that module.
76249
@@ -81,14 +254,14 @@ For items which are publicly exposed by a module, the rules are that:
81254 * If it is an ` fn ` declaration, items referred to in its trait bounds, argument
82255 types, and return type must be public.
83256
84- * If it is a ` struct ` or ` enum ` declaration, items referred to in its trait
257+ * If it is a ` struct ` or ` enum ` declaration, items referred to in its trait
85258 bounds and in the types of its ` pub ` fields must be public.
86259
87- * If it is a ` type ` declaration, items referred to in its definition must be
260+ * If it is a ` type ` declaration, items referred to in its definition must be
88261 public.
89262
90263 * If it is a ` trait ` declaration, items referred to in its super-traits, in the
91- trait bounds of its type parameters, and in the signatures of its methods
264+ trait bounds of its type parameters, and in the signatures of its methods
92265 (see ` fn ` case above) must be public.
93266
94267
@@ -97,16 +270,16 @@ For items which are publicly exposed by a module, the rules are that:
97270An item ` Item ` referred to in the module ` module ` is considered to be public if:
98271
99272 * The qualified name used by ` module ` to refer to ` Item ` , when recursively
100- resolved through ` use ` declarations back to the original declaration of
273+ resolved through ` use ` declarations back to the original declaration of
101274 ` Item ` , resolves along the way to at least one ` pub ` declaration, whether a
102275 ` pub use ` declaration or a ` pub ` original declaration; and
103276
104- * For at least one of the above resolved-to ` pub ` declarations, all ancestor
277+ * For at least one of the above resolved-to ` pub ` declarations, all ancestor
105278 modules of the declaration, up to the deepest common ancestor module of the
106279 declaration with ` module ` , are ` pub ` .
107-
280+
108281In all other cases, an ` Item ` referred to in ` module ` is not considered to be
109- public, or ` module ` itself cannot refer to ` Item ` and the distinction is
282+ public, or ` module ` itself cannot refer to ` Item ` and the distinction is
110283irrelevant.
111284
112285### Examples
@@ -182,13 +355,13 @@ pub mod x {
182355}
183356````
184357
185- In the above examples, it is assumed that ` module ` will refer to ` Item ` as
358+ In the above examples, it is assumed that ` module ` will refer to ` Item ` as
186359simply ` Item ` , but the same thing holds true if ` module ` refrains from importing
187360` Item ` explicitly with a private ` use ` declaration, and refers to it directly by
188361qualifying it with a path instead.
189362
190363
191- In the below examples, the item ` Item ` referred to in the module ` module ` is
364+ In the below examples, the item ` Item ` referred to in the module ` module ` is
192365* not* considered to be public:
193366
194367````
@@ -236,46 +409,26 @@ pub mod x {
236409
237410# Drawbacks
238411
239- Requires effort to implement.
240-
241- May break existing code.
242-
243- It may turn out that there are use cases which become inexpressible. If there
244- are, we should consider solutions to them on a case-by-case basis.
245-
246- One such use case is constraining types in the public interface to a trait,
247- but not permitting anyone outside the module to implement the trait. For
248- instance:
249-
250- trait Private {}
251- pub fn public<T: Private>() { ... }
252-
253- Similarly, you may want a public trait which is closed to external
254- implementations:
412+ Adds a (temporary) feature gate.
255413
256- pub trait MyClosedTrait: Private { ... }
414+ Requires some existing code to opt-in to the feature gate before transitioning
415+ to saner alternatives.
257416
258- I believe that if these use cases are deemed important, then they should be
259- addressed directly, by allowing traits to be declared closed to external
260- implementations explicitly. Possible syntaxes include:
417+ Requires effort to implement.
261418
262- ````
263- #[no_external_impls]
264- pub trait MyClosedTrait { ... }
265- ````
266419
267- ````
268- pub closed trait MyClosedTrait { ... }
269- ````
420+ # Alternatives
270421
271- Adding this is not an integral part of this RFC, and my preference would be to
272- discuss it separately. It's only an option in case the mentioned functionality
273- is considered too valuable to lose even temporarily, which I personally doubt.
422+ If we stick with the status quo, we'll have to resolve several bizarre questions
423+ and keep supporting its behavior indefinitely after 1.0.
274424
275- # Alternatives
425+ Instead of a feature gate, we could just ban these things outright right away,
426+ at the cost of temporarily losing some convenience and a small amount of
427+ expressiveness before the more principled replacement features are implemented.
276428
277- The alternative is the status quo, and the impact of not doing this is that
278- we'll have to live with it forever. * (dramatic music)*
429+ We could make an exception for private supertraits, as these are not quite as
430+ problematic as the other cases. However, especially given that a more principled
431+ alternative is known (private methods), I would rather not make any exceptions.
279432
280433
281434# Unresolved questions
@@ -286,4 +439,4 @@ Did I describe them correctly in the "Detailed design"?
286439
287440Did I miss anything? Are there any holes or contradictions?
288441
289- Is there a simpler, easier, and/or more logical formulation?
442+ Is there a simpler, easier, and/or more logical formulation of the rules ?
0 commit comments