Skip to content

Commit 3cd79b6

Browse files
committed
Amend motivation, add more discussion of older version.
1 parent 2e88bc7 commit 3cd79b6

File tree

1 file changed

+45
-242
lines changed

1 file changed

+45
-242
lines changed

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

+45-242
Original file line numberDiff line numberDiff line change
@@ -8,254 +8,52 @@ Require a feature gate to expose private items in public APIs, until we grow the
88
appropriate language features to be able to remove the feature gate and forbid
99
it entirely.
1010

11-
1211
# Motivation
1312

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.
16-
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.
23-
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.
27-
28-
[questions]: https://github.com/rust-lang/rust/issues/10573
29-
30-
## Examples of strangeness
31-
32-
(See also https://github.com/rust-lang/rust/issues/10573.)
33-
34-
* Being able to use a given type but not *write* the type is disconcerting.
35-
For example:
36-
37-
struct Foo { ... }
38-
pub fn foo() -> Foo { ... }
39-
40-
As a client of this module, I can write `let my_foo = foo();`, but not
41-
`let my_foo: Foo = foo();`. This is a logical consequence of the rules, but
42-
it doesn't make any sense.
43-
44-
* Can I access public fields of a private type? For instance:
45-
46-
struct Foo { pub x: int, ... }
47-
pub fn foo() -> Foo { ... }
48-
49-
Can I now write `foo().x`, even though the struct `Foo` itself is not visible
50-
to me, and in `rust-doc`, I couldn't see its documentation? In other words,
51-
when the only way I could learn about the field `x` is by looking at the
52-
source code for the given module?
53-
54-
* Can I call public methods on a private type?
55-
56-
* Can I "know about" `trait`s the private type `impl`s?
57-
58-
Again, it's surely possible to formulate rules such that answers to these
59-
questions can be deduced from them mechanically. But that doesn't mean it's a
60-
good idea to do so. If the results are bizarre, then our assumptions should be
61-
reconsidered. In these cases, it would be wiser to simply say, "don't do that".
62-
63-
## Properties
64-
65-
By restricting public APIs to only mentioning public items, we can guarantee that:
66-
67-
*Only public definitions are reachable through the public surface area of an API.*
68-
69-
Or in other words: for any item I see mentioned in `rust-doc` generated
70-
documentation, I can *always* click it to see *its* documentation, in turn.
71-
72-
Or, dually:
73-
74-
*The presence or absence of private definitions should not be observable or
75-
discoverable through the public API.*
76-
77-
As @aturon put it:
78-
79-
> One concrete problem with allowing private items to leak is that you lose some
80-
> local reasoning. You might expect that if an item is marked private, you can
81-
> refactor at will without breaking clients. But with leakage, you can't make
82-
> this determination based on the item alone: you have to look at the entire API
83-
> to spot leakages (or, I guess, have the lint do so for you). Perhaps not a
84-
> huge deal in practice, but worrying nonetheless.
85-
86-
87-
## Use cases for exposing private items, and preferable solutions
88-
89-
### Abstract types
90-
91-
One may wish to use a private type in a public API to hide its implementation,
92-
either by using the private type in the API directly, or by defining a
93-
`pub type` synonym for it.
94-
95-
The correct solution in this case is to use a newtype instead. However, this can
96-
currently be an unacceptably heavyweight solution in some cases, because one
97-
must manually write all of the trait `impl`s to forward from the newtype to the
98-
old type. This should be resolved by adding a [newtype deriving feature][gntd]
99-
along the same lines as GHC (based on the same infrastructure as
100-
[`Transmute`][91], née `Coercible`), or possibly with first-class module-scoped
101-
existential types a la ML.
102-
103-
[gntd]: https://www.haskell.org/ghc/docs/7.8.1/html/users_guide/deriving.html#newtype-deriving
104-
[91]: https://github.com/rust-lang/rfcs/pull/91
105-
106-
107-
### Private supertraits
108-
109-
A use case for private supertraits currently is to prevent outside modules from
110-
implementing the given trait, and potentially to have a private interface for
111-
the given types which is accessible only from within the given module. For
112-
example:
113-
114-
trait PrivateInterface {
115-
fn internal_id(&self) -> uint;
116-
}
117-
118-
pub trait PublicInterface: PrivateInterface {
119-
fn name(&self) -> String;
120-
...
121-
}
122-
123-
pub struct Foo { ... }
124-
pub struct Bar { ... }
125-
126-
impl PrivateInterface for Foo { ... }
127-
impl PublicInterface for Foo { ... }
128-
impl PrivateInterface for Bar { ... }
129-
impl PublicInterface for Bar { ... }
130-
131-
pub fn do_thing_with<T: PublicInterface>(x: &T) {
132-
// PublicInterface implies PrivateInterface!
133-
let id = x.internal_id();
134-
...
135-
}
136-
137-
Here `PublicInterface` may only be implemented by us, because it requires
138-
`PrivateInterface` as a supertrait, which is not exported outside the module.
139-
Thus `PublicInterface` is only implemented by a closed set of types which we
140-
specify. Public functions may require `PublicInterface` to be generic over this
141-
closed set of types, and in their implementations, they may also use the methods
142-
of the private `PrivateInterface` supertrait.
143-
144-
The better solution for this use case, which doesn't require exposing
145-
a `PrivateInterface` in the public-facing parts of the API, would be to have
146-
private trait methods. This can be seen by considering the analogy of `trait`s
147-
as generic `struct`s and `impl`s as `static` instances of those `struct`s (with
148-
the compiler selecting the appropriate instance based on type inference).
149-
Supertraits can also be modelled as additional fields.
150-
151-
For example:
152-
153-
pub trait Eq {
154-
fn eq(&self, other: &Self) -> bool;
155-
fn ne(&self, other: &Self) -> bool;
156-
}
157-
158-
impl Eq for Foo {
159-
fn eq(&self, other: &Foo) -> bool { /* def eq */ }
160-
fn ne(&self, other: &Foo) -> bool { /* def ne */ }
161-
}
162-
163-
This corresponds to:
164-
165-
pub struct Eq<Self> {
166-
pub eq: fn(&Self, &Self) -> bool,
167-
pub ne: fn(&Self, &Self) -> bool
168-
}
169-
170-
pub static EQ_FOR_FOO: Eq<Foo> = {
171-
eq: |&this, &other| { /* def eq */ },
172-
ne: |&this, &other| { /* def ne */ }
173-
};
174-
175-
Now if we consider the private supertrait example from above, that becomes:
176-
177-
struct PrivateInterface<Self> {
178-
pub internal_id: fn(&Self) -> uint
179-
}
180-
181-
pub struct PublicInterface<Self> {
182-
pub super0: PrivateInterface<Self>,
183-
pub name: fn(&Self) -> String
184-
};
185-
186-
We can see that this solution is analogous to the same kind of
187-
private-types-in-public-APIs situation which we want to forbid. And it sheds
188-
light on a hairy question which had been laying hidden beneath the surface:
189-
outside modules can't see `PrivateInterface`, but can they see `internal_id`?
190-
We had been assuming "no", because that was convenient, but rigorously thinking
191-
it through, `trait` methods are conceptually public, so this wouldn't
192-
*necessarily* be the "correct" answer.
193-
194-
The *right* solution here is the same as for `struct`s: private fields, or
195-
correspondingly, private methods. In other words, if we were working with
196-
`struct`s and `static`s directly, we would write:
197-
198-
pub struct PublicInterface<Self> {
199-
pub name: fn(&Self) -> String,
200-
internal_id: fn(&Self) -> uint
201-
}
202-
203-
so the public data is public and the private data is private, no mucking around
204-
with the visibility of their *types*. Correspondingly, we would like to write
205-
something like:
206-
207-
pub trait PublicInterface {
208-
fn name(&self) -> String;
209-
priv fn internal_id(&self) -> uint;
210-
}
211-
212-
(Note that this is **not** a suggestion for particular syntax.)
213-
214-
If we can write this, everything we want falls out straightforwardly.
215-
`internal_id` is only visible inside the given module, and outside modules can't
216-
access it. Furthermore, just as you can't construct a (`static` or otherwise)
217-
instance of a `struct` if it has inaccessible private fields, you also can't
218-
construct an `impl` of a `trait` if it has inaccessible private methods.
219-
220-
So private supertraits should also be put behind a feature gate, like everything
221-
else, until we figure out how to add private `trait` methods.
222-
13+
Privacy is central to guaranteeing the invariants necessary to write
14+
correct code that employs unsafe blocks. Although the current language
15+
rules prevent a private item from being directly named from outside
16+
the current module, they still permit direct access to private items
17+
in some cases. For example, a public function might return a value of
18+
private type. A caller from outside the module could then invoke this
19+
function and, thanks to type inference, gain access to the private
20+
type (though they still could not invoke public methods or access
21+
public fields). This access could undermine the reasoning of the
22+
author of the module. Fortunately, it is not hard to prevent.
22323

22424
# Detailed design
22525

22626
## Overview
22727

22828
The general idea is that:
22929

230-
* If an item is publicly exposed by a module `module`, items referred to in
231-
the public-facing parts of that item (e.g. its type) must themselves be
232-
public.
233-
234-
* An item referred to in `module` is considered to be public if it is visible
235-
to clients of `module`.
30+
* If an item is declared as public, items referred to in the
31+
public-facing parts of that item (e.g. its type) must themselves be
32+
declared as public.
23633

23734
Details follow.
23835

239-
24036
## The rules
24137

24238
These rules apply as long as the feature gate is not enabled. After the feature
24339
gate has been removed, they will apply always.
24440

245-
An item is considered to be publicly exposed by a module if it is declared `pub`
246-
by that module, or if it is re-exported using `pub use` by that module.
41+
### When is an item "public"?
24742

248-
Items in a `impl` of a trait (not an inherent impl) are considered public
249-
if all of the following conditions are met:
43+
Items that are explicitly declared as `pub` are always public. In
44+
addition, items in the `impl` of a trait (not an inherent impl) are
45+
considered public if all of the following conditions are met:
25046

25147
* The trait being implemented is public.
25248
* All input types (currently, the self type) of the impl are public.
25349
* *Motivation:* If any of the input types or the trait is public, it
25450
should be impossible for an outside to access the items defined in
25551
the impl. They cannot name the types nor they can get direct access
25652
to a value of those types.
53+
54+
### What restrictions apply to public items?
25755

258-
For items which are publicly exposed by a module, the rules are that:
56+
The rules for various kinds of public items are as follows:
25957

26058
* If it is a `static` declaration, items referred to in its type must be public.
26159

@@ -272,10 +70,6 @@ For items which are publicly exposed by a module, the rules are that:
27270
trait bounds of its type parameters, and in the signatures of its methods
27371
(see `fn` case above) must be public.
27472

275-
## What does "public" mean?
276-
277-
An item is considered "public" if it is declared with the `pub` qualifier.
278-
27973
### Examples
28074

28175
Here are some examples to demonstrate the rules.
@@ -453,20 +247,29 @@ We could make an exception for private supertraits, as these are not quite as
453247
problematic as the other cases. However, especially given that a more principled
454248
alternative is known (private methods), I would rather not make any exceptions.
455249

456-
The original design defined "public items" using a reachability
457-
predicate. This allowed private items to be exported via `pub use` and
458-
hence considered public. Unfortunately, this design makes it difficult
459-
to determine at a glance whether any particular item was exposed
460-
outside the current module or not, as one must search for a `pub
461-
use`. Moreover, it does not add expressiveness, as demonstrated in the
462-
examples section.
463-
464-
# Unresolved questions
465-
466-
Is this the right set of rules to apply?
467-
468-
Did I describe them correctly in the "Detailed design"?
469-
470-
Did I miss anything? Are there any holes or contradictions?
250+
The original design of this RFC had a stronger notion of "public"
251+
which also considered whether a public path existed to the item. In
252+
other words, a module `X` could not refer to a public item `Y` from a
253+
submodule `Z`, unless `X` also exposed a public path to `Y` (whether
254+
that be because `Z` was public, or via a `pub use`). This definition
255+
strengthened the basic guarantee of "private things are only directly
256+
accessible from within the current module" to include the idea that
257+
public functions in outer modules cannot accidentally refer to public
258+
items from inner modules unless there is a public path from the outer
259+
to the inner module. Unfortunately, these rules were complex to state
260+
concisely and also hard to understand in practice; when an error
261+
occurred under these rules, it was very hard to evaluate whether the
262+
error was legitimate. The newer rules are simpler while still
263+
retaining the basic privacy guarantee.
264+
265+
One important advantage of the earlier approach, and a scenario not
266+
directly addressed in this RFC, is that there may be items which are
267+
declared as public by an inner module but *still* not intended to be
268+
exposed to the world at large (in other words, the items are only
269+
expected to be used within some subtree). A special case of this is
270+
crate-local data. In the older rules, the "intended scope" of privacy
271+
could be somewhat inferred from the existence (or non-existence) of
272+
`pub use` declarations. However, in the author's opinion, this
273+
scenario would be best addressed by making `pub` declarations more
274+
expressive so that the intended scope can be stated directly.
471275

472-
Is there a simpler, easier, and/or more logical formulation of the rules?

0 commit comments

Comments
 (0)