Skip to content

Commit 20c5714

Browse files
committed
greatly expand and sharpen the Motivation section based on discussion in the comment thread
1 parent 2346d7e commit 20c5714

File tree

1 file changed

+215
-62
lines changed

1 file changed

+215
-62
lines changed

active/0000-no-privates-in-public.md

Lines changed: 215 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,31 @@
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

4959
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.
54224

55225

56226
# Detailed design
@@ -60,17 +230,20 @@ should be revisited. In these cases, it would be cleaner to simply say,
60230
The 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

69239
Details 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+
74247
An item is considered to be publicly exposed by a module if it is declared `pub`
75248
by 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:
97270
An 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+
108281
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
110283
irrelevant.
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
186359
simply `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
188361
qualifying 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

287440
Did 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

Comments
 (0)