4
4
5
5
# Summary
6
6
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.
8
10
9
11
10
12
# Motivation
11
13
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.
13
16
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.
16
23
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.
19
27
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
22
29
23
- ## Examples
30
+
31
+ ## Examples of strangeness
24
32
25
33
(See also https://github.com/rust-lang/rust/issues/10573 .)
26
34
@@ -35,22 +43,184 @@ able to click it to see its documentation in turn.
35
43
it doesn't make any sense.
36
44
37
45
* Can I access public fields of a private type? For instance:
38
-
46
+
39
47
struct Foo { pub x: int, ... }
40
48
pub fn foo() -> Foo { ... }
41
49
42
50
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,
44
52
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?
46
54
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?
48
58
49
59
Again, 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.
54
224
55
225
56
226
# Detailed design
@@ -60,17 +230,20 @@ should be revisited. In these cases, it would be cleaner to simply say,
60
230
The general idea is that:
61
231
62
232
* 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
64
234
public.
65
235
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
67
237
to clients of ` module ` .
68
238
69
239
Details follow.
70
240
71
241
72
242
## The rules
73
243
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
+
74
247
An item is considered to be publicly exposed by a module if it is declared ` pub `
75
248
by that module, or if it is re-exported using ` pub use ` by that module.
76
249
@@ -81,14 +254,14 @@ For items which are publicly exposed by a module, the rules are that:
81
254
* If it is an ` fn ` declaration, items referred to in its trait bounds, argument
82
255
types, and return type must be public.
83
256
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
85
258
bounds and in the types of its ` pub ` fields must be public.
86
259
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
88
261
public.
89
262
90
263
* 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
92
265
(see ` fn ` case above) must be public.
93
266
94
267
@@ -97,16 +270,16 @@ For items which are publicly exposed by a module, the rules are that:
97
270
An item ` Item ` referred to in the module ` module ` is considered to be public if:
98
271
99
272
* 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
101
274
` Item ` , resolves along the way to at least one ` pub ` declaration, whether a
102
275
` pub use ` declaration or a ` pub ` original declaration; and
103
276
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
105
278
modules of the declaration, up to the deepest common ancestor module of the
106
279
declaration with ` module ` , are ` pub ` .
107
-
280
+
108
281
In 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
110
283
irrelevant.
111
284
112
285
### Examples
@@ -182,13 +355,13 @@ pub mod x {
182
355
}
183
356
````
184
357
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
186
359
simply ` Item ` , but the same thing holds true if ` module ` refrains from importing
187
360
` Item ` explicitly with a private ` use ` declaration, and refers to it directly by
188
361
qualifying it with a path instead.
189
362
190
363
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
192
365
* not* considered to be public:
193
366
194
367
````
@@ -236,46 +409,26 @@ pub mod x {
236
409
237
410
# Drawbacks
238
411
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.
255
413
256
- pub trait MyClosedTrait: Private { ... }
414
+ Requires some existing code to opt-in to the feature gate before transitioning
415
+ to saner alternatives.
257
416
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.
261
418
262
- ````
263
- #[no_external_impls]
264
- pub trait MyClosedTrait { ... }
265
- ````
266
419
267
- ````
268
- pub closed trait MyClosedTrait { ... }
269
- ````
420
+ # Alternatives
270
421
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.
274
424
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.
276
428
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.
279
432
280
433
281
434
# Unresolved questions
@@ -286,4 +439,4 @@ Did I describe them correctly in the "Detailed design"?
286
439
287
440
Did I miss anything? Are there any holes or contradictions?
288
441
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