Skip to content
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

Amend RFC 48 #200

Merged
merged 2 commits into from
Sep 18, 2014
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
248 changes: 139 additions & 109 deletions active/0048-no-privates-in-public.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ case), and this is the mechanism which should be used for it.

[questions]: https://github.com/rust-lang/rust/issues/10573


## Examples of strangeness

(See also https://github.com/rust-lang/rust/issues/10573.)
Expand Down Expand Up @@ -61,7 +60,6 @@ questions can be deduced from them mechanically. But that doesn't mean it's a
good idea to do so. If the results are bizarre, then our assumptions should be
reconsidered. In these cases, it would be wiser to simply say, "don't do that".


## Properties

By restricting public APIs to only mentioning public items, we can guarantee that:
Expand Down Expand Up @@ -247,6 +245,16 @@ gate has been removed, they will apply always.
An item is considered to be publicly exposed by a module if it is declared `pub`
by that module, or if it is re-exported using `pub use` by that module.

Items in a `impl` of a trait (not an inherent impl) are considered public
if all of the following conditions are met:

* The trait being implemented is public.
* All input types (currently, the self type) of the impl are public.
* *Motivation:* If any of the input types or the trait is public, it
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be "are not public"?

should be impossible for an outside to access the items defined in
the impl. They cannot name the types nor they can get direct access
to a value of those types.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thinking was that trait impls do not create items which can be referenced in APIs. You refer to the trait and its methods; these are then resolved during typechecking (not name resolution!) to a particular impl (if possible), but this happens behind the scenes and is opaque to the code doing the referencing. Is this point of view Haskell-centric and not true in Rust?


For items which are publicly exposed by a module, the rules are that:

* If it is a `static` declaration, items referred to in its type must be public.
Expand All @@ -263,160 +271,175 @@ For items which are publicly exposed by a module, the rules are that:
* If it is a `trait` declaration, items referred to in its super-traits, in the
trait bounds of its type parameters, and in the signatures of its methods
(see `fn` case above) must be public.



## What does "public" mean?

An item `Item` referred to in the module `module` is considered to be public if:

* The qualified name used by `module` to refer to `Item`, when recursively
resolved through `use` declarations back to the original declaration of
`Item`, resolves along the way to at least one `pub` declaration, whether a
`pub use` declaration or a `pub` original declaration; and

* For at least one of the above resolved-to `pub` declarations, all ancestor
modules of the declaration, up to the deepest common ancestor module of the
declaration with `module`, are `pub`.

In all other cases, an `Item` referred to in `module` is not considered to be
public, or `module` itself cannot refer to `Item` and the distinction is
irrelevant.
An item is considered "public" if it is declared with the `pub` qualifier.

### Examples

In the following examples, the item `Item` referred to in the module `module`
is considered to be public:
Here are some examples to demonstrate the rules.

````
pub mod module {
pub struct Item { ... }
}
````
#### Struct fields

````
pub struct Item { ... }
pub mod module {
use Item;
// A private struct may refer to any type in any field.
struct Priv {
a: Priv,
b: Pub,
pub c: Priv
}
````

````
pub mod x {
pub struct Item { ... }
enum Vapor<A> { X, Y, Z } // Note that A is not used

// Public fields of a public struct may only refer to public types.
pub struct Item {
// Private field may reference a private type.
a: Priv,

// Public field must refer to a public type.
pub b: Pub,

// ERROR: Public field refers to a private type.
pub c: Priv,

// ERROR: Public field refers to a private type.
// For the purposes of this test, we do not descend into the type,
// but merely consider the names that appear in type parameters
// on the type, regardless of usage (or lack thereof) within the type
// definition itself.
pub d: Vapor<Priv>,
}
pub mod module {
use x::Item;
}
````

pub struct Pub { ... }
````
pub mod module {
pub mod x {
pub struct Item { ... }
}
use self::x::Item;

#### Methods

```
struct Priv { .. }
pub struct Pub { .. }
pub struct Foo { .. }

impl Foo {
// Illegal: public method with argument of private type.
pub fn foo(&self, p: Priv) { .. }
}
````
```

````
struct Item { ... }
pub mod module {
pub use Item;
#### Trait bounds

```
trait PrivTrait { ... }

// Error: type parameter on public item bounded by a private trait.
pub struct Foo<X: PrivTrait> { ... }

// OK: type parameter on private item.
struct Foo<X: PrivTrait> { ... }
```

#### Trait definitions

```
struct PrivStruct { ... }

pub trait PubTrait {
// Error: private struct referenced from method in public trait
fn method(x: PrivStruct) { ... }
}
````

````
struct Foo { ... }
pub use Item = Foo;
pub mod module {
use Item;
trait PrivTrait {
// OK: private struct referenced from method in private trait
fn method(x: PrivStruct) { ... }
}
````
```

````
struct Foo { ... }
pub use Bar = Foo;
use Item = Bar;
pub mod module {
use Item;
#### Implementations

To some extent, implementations are prevented from exposing private
types because their types must match the trait. However, that is not
true with generics.

```
pub trait PubTrait<T> {
fn method(t: T);
}
````

````
struct Item { ... }
pub mod x {
pub use Item;
pub mod y {
use x::Item;
pub mod module {
use super::Item;
}
struct PubStruct { ... }

struct PrivStruct { ... }

impl PubTrait<PrivStruct> for PubStruct {
// ^~~~~~~~~~ Error: Private type referenced from impl of
// public trait on a public type. [Note: this is
// an "associated type" here, not an input.]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is... interesting. At first I was going to write:

For the same reason, it's not clear to me that this should be forbidden. Client code sees traits and their associated items; the typechecker sees the impls. Going with associated types syntax, client code can refer to the given type as fn some_func<T: PubTrait>(arg: T::AssocType); the fact that this may resolve to a private type doesn't seem significant, because it's abstract to some_func.

But, if you write fn other_func(arg: PubStruct::AssocType), that's no longer the case (this is very similar to pub type NotAssocType = PrivStruct). So I think you're in fact correct that this should be forbidden.


fn method(t: PrivStruct) {
// ^~~~~~~~~~ Error: Private type in method signature.
//
// Implementation note. It may not be a good idea to report
// an error here; I think private types can only appear in
// an impl by having an associated type bound to a private
// type.
}
}
````
```

In the above examples, it is assumed that `module` will refer to `Item` as
simply `Item`, but the same thing holds true if `module` refrains from importing
`Item` explicitly with a private `use` declaration, and refers to it directly by
qualifying it with a path instead.
#### Type aliases

Note that the path to the public item does not have to be private.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean "have to be public"?


In the below examples, the item `Item` referred to in the module `module` is
*not* considered to be public:

````
pub mod module {
struct Item { ... }
```
mod impl {
pub struct Foo { ... }
}
````
pub type Bar = self::impl::Foo;
```

### Negative examples

The following examples should fail to compile under these rules.

#### Non-public items referenced by a pub use

These examples are illegal because they use a `pub use` to re-export
a private item:

````
struct Item { ... }
pub mod module {
use Item;
// Error: Item is not declared as public, but is referenced from
// a `pub use`.
pub use Item;
}
````

````
mod x {
pub struct Item { ... }
}
pub mod module {
use x::Item;
}
struct Foo { ... }
// Error: Non-public item referenced by `pub use`.
pub use Item = Foo;
````

````
pub mod module {
use self::x::Item;
mod x {
pub struct Item { ... }
}
}
````
If it was desired to have a private name that is publicly "renamed" using a pub
use, that can be achieved using a module:

````
struct Item { ... }
pub use Alias = Item;
pub mod x {
pub use Item;
pub mod module {
use Item; // refers to top-level `Item`
}
```
mod impl {
pub struct ItemPriv;
}
````

pub use Item = self::impl::ItemPriv;
```

# Drawbacks

Adds a (temporary) feature gate.

Requires some existing code to opt-in to the feature gate before transitioning
to saner alternatives.
Requires some existing code to opt-in to the feature gate before
transitioning to a more explicit alternative.

Requires effort to implement.


# Alternatives

If we stick with the status quo, we'll have to resolve several bizarre questions
Expand All @@ -430,6 +453,13 @@ We could make an exception for private supertraits, as these are not quite as
problematic as the other cases. However, especially given that a more principled
alternative is known (private methods), I would rather not make any exceptions.

The original design defined "public items" using a reachability
predicate. This allowed private items to be exported via `pub use` and
hence considered public. Unfortunately, this design makes it difficult
to determine at a glance whether any particular item was exposed
outside the current module or not, as one must search for a `pub
use`. Moreover, it does not add expressiveness, as demonstrated in the
examples section.

# Unresolved questions

Expand Down