api file default public (#752)

Replace library public-like `api` behavior with explicit `private` behavior, mirroring #665 

Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
This commit is contained in:
Jon Meow
2021-09-21 09:33:32 -07:00
committed by GitHub
co-authored by Chandler Carruth
parent b1af08fed0
commit 6d33a80988
3 changed files with 217 additions and 21 deletions
@@ -339,28 +339,27 @@ for separate compilation.
#### Exporting entities from an API file
In order to actually be part of a library's API, entities must both be in the
API file and explicitly marked as an API. This is done using the `api` keyword,
which is only allowed in the API file. For example:
Entities in the API file are part of the library's public API by default. They
may be marked as `private` to indicate they should only be visible to other
parts of the library.
```carbon
package Geometry library "Shapes" api;
// Circle is marked as an API, and will be available to other libraries as
// Geometry.Circle.
api struct Circle { ... }
// Circle is an API, and will be available to other libraries as
Geometry.Circle.
struct Circle { ... }
// CircleHelper is not marked as an API, and so will not be available to other
// libraries.
fn CircleHelper(Circle circle) { ... }
// CircleHelper is private, and so will not be available to other libraries.
private fn CircleHelper(Circle circle) { ... }
// Only entities in namespaces should be marked as an API, not the namespace
// itself.
namespace Operations;
// Operations.GetCircumference is marked as an API, and will be available to
// Operations.GetCircumference is an API, and will be available to
// other libraries as Geometry.Operations.GetCircumference.
api fn Operations.GetCircumference(Circle circle) { ... }
fn Operations.GetCircumference(Circle circle) { ... }
```
This means that an API file can contain all implementation code for a library.
@@ -374,7 +373,9 @@ However, separate implementation files are still desirable for a few reasons:
- From a code maintenance perspective, having smaller files can make a library
more maintainable.
Use of the `api` keyword is not allowed within files marked as `impl`.
Entities in the `impl` file should never have visibility keywords. If they are
forward declared in the `api` file, they use the declaration's visibility; if
they are only present in the `impl` file, they are implicitly `private`.
#### Granularity of libraries
@@ -404,7 +405,7 @@ package Checksums library "Sha" api;
namespaces Sha256;
api fn Sha256.HexDigest(Bytes data) -> String { ... }
fn Sha256.HexDigest(Bytes data) -> String { ... }
```
Calling code may look like:
@@ -434,7 +435,7 @@ import IDENTIFIER (library NAME_PATH)?;
```
An import declares a package entity named after the imported package, and makes
`api`-tagged entities from the imported library through it. The full name path
API entities from the imported library available through it. The full name path
is a concatenation of the names of the package entity, any namespace entities
applied, and the final entity addressed. Child namespaces or entities may be
[aliased](/docs/design/aliases.md) if desired.
@@ -444,7 +445,7 @@ For example, given a library:
```carbon
package Math api;
namespace Trigonometry;
api fn Trigonometry.Sin(...);
fn Trigonometry.Sin(...);
```
Calling code would import it and use it like:
@@ -574,12 +575,12 @@ server for open source packages. Conflicts can also be addressed by renaming one
of the packages, either at the source, or as a local modification.
We do need to address the case of package names conflicting with other entity
names. It's possible that a pre-existing `api` entity will conflict with a new
import, and that the `api` is infeasible to rename due to existing callers.
Alternately, the `api` entity may be using an idiomatic name that it would
contradict naming conventions to rename. In either case, this conflict may exist
in a single file without otherwise affecting users of the API. This will be
addressed by [name lookup](/docs/design/name_lookup.md).
names. It's possible that a pre-existing entity will conflict with a new import,
and that renaming the entity is infeasible to rename due to existing callers.
Alternately, the entity may be using an idiomatic name that it would contradict
naming conventions to rename. In either case, this conflict may exist in a
single file without otherwise affecting users of the API. This will be addressed
by [name lookup](/docs/design/name_lookup.md).
### Potential refactorings
@@ -816,10 +817,13 @@ should be part of a larger testing plan.
- [Collapse file and library concepts](/proposals/p0107.md#collapse-file-and-library-concepts)
- [Collapse the library concept into packages](/proposals/p0107.md#collapse-the-library-concept-into-packages)
- [Collapse the package concept into libraries](/proposals/p0107.md#collapse-the-package-concept-into-libraries)
- [Default `api` to private](/proposals/p0752.md#default-api-to-private)
- [Default `impl` to public](/proposals/p0752.md#default-impl-to-public)
- [Different file type labels](/proposals/p0107.md#different-file-type-labels)
- [Function-like syntax](/proposals/p0107.md#function-like-syntax)
- [Inlining from implementation files](/proposals/p0107.md#inlining-from-implementation-files)
- [Library-private access controls](/proposals/p0107.md#library-private-access-controls)
- [Make keywords either optional or required in separate definitions](/proposals/p0752.md#make-keywords-either-optional-or-required-in-separate-definitions)
- [Managing API versus implementation in libraries](/proposals/p0107.md#managing-api-versus-implementation-in-libraries)
- [Multiple API files](/proposals/p0107.md#multiple-api-files)
- [Name paths as library names](/proposals/p0107.md#name-paths-as-library-names)
+1
View File
@@ -69,6 +69,7 @@ request:
- [0680 - And, or, not](p0680.md)
- [0722 - Nominal classes and methods](p0722.md)
- [0731 - Generics details 2: adapters, associated types, parameterized interfaces](p0731.md)
- [0752 - `api` file default-`public`](p0752.md)
- [0777 - Inheritance](p0777.md)
<!-- endproposals -->
+191
View File
@@ -0,0 +1,191 @@
# `api` file default-`public`
<!--
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
Exceptions. See /LICENSE for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-->
[Pull request](https://github.com/carbon-language/carbon-lang/pull/752)
<!-- toc -->
## Table of contents
- [Problem](#problem)
- [Background](#background)
- [Proposal](#proposal)
- [Rationale based on Carbon's goals](#rationale-based-on-carbons-goals)
- [Alternatives considered](#alternatives-considered)
- [Default `api` to private](#default-api-to-private)
- [Default `impl` to public](#default-impl-to-public)
- [Make keywords either optional or required in separate definitions](#make-keywords-either-optional-or-required-in-separate-definitions)
<!-- tocstop -->
## Problem
Question for leads
[#665: private vs public _syntax_ strategy, as well as other visibility tools like external/api/etc.](https://github.com/carbon-language/carbon-lang/issues/665)
decided that methods on classes should default to public. Should `api` echo the
similar strategy?
## Background
- In C++, `struct` members default public, while `class` members default
`public`.
- In proposal
[#107: Code and name organization](https://github.com/carbon-language/carbon-lang/pull/107),
an `api` keyword was used to indicate public APIs within an `api` file.
- In [#665](https://github.com/carbon-language/carbon-lang/issues/665), it was
decided that Carbon class members should default `public`.
- This issue was reopened to discuss alternatives in this proposal.
## Proposal
APIs in the `api` file should default public, without need for an additional
`api` keyword. `private` may be specified to designate APIs that are internal to
the library, and only visible to `impl` files.
Nothing is necessary within `impl` files, and APIs there will be private unless
forward declared in the `api` file.
## Rationale based on Carbon's goals
- [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write):
It will be easier for developers to understand code if APIs have
semantically similar behavior when comparing the visibility of class methods
to other code, and the library to other packages.
## Alternatives considered
### Default `api` to private
Default private is what was implied by `api`, and was the previous state.
Advantages:
- Decreases the likelihood that developers will accidentally expose APIs,
because it's an explicit choice.
- Can move functions between `api` and `impl` without visibility changing.
Disadvantages:
- The `api` file's primary purpose is to expose APIs, and so it may be more
natural for developers to assume things there are public.
- Inconsistent with "default public" behavior on classes.
We are switching to default public in `api` files for consistency with class
behaviors.
### Default `impl` to public
Noting that we default `api` to public, we could similarly default `impl` to
public.
Advantages:
- Can move functions between `api` and `impl` without visibility changing.
Disadvantages:
- Everything in an `impl` file must be private unless it's a separate
definition of an `api` declaration. As a consequence, everything declared in
the `impl` file would need to be explicitly `private`.
In order to avoid the toil of explicitly declaring everything in the `impl` as
`private`, `impl` will be `private` by default. As a consequence of being the
default behavior, no `private` should be specified, just as `public` is not
allowed in `api` files.
Note the visibility behavior can be described as making declarations the most
visible possible for its context, which in `api` files is `public`, and in
`impl` is `private`.
### Make keywords either optional or required in separate definitions
When a prior declaration exists, keywords are _disallowed_ in separate
definitions. We could instead allow keywords, making them either optional or
required. This would allow developers to determine visibility when reading a
definition.
The downside of this is that optional keywords can be confusing. For example:
- `api` file:
```
class Foo {
private fn Bar();
private fn Wiz();
};
```
- `impl` file:
```
fn Foo.Bar() { ...impl... }
private fn Foo.Wiz() { ...impl... }
fn Baz() { ...impl... }
```
In an "optional" setup, the above is valid code. However, the lack of a
`private` keyword on `Foo.Bar` may lead a developer to conclude that it's public
without checking the `api` file (particularly because `Foo.Wiz` is explicitly
private), when it's actually private. This is an accident that could also occur
on refactoring; for example, removing the keyword on the `impl` version of
`Foo.Wiz` would be valid but does not make it public.
A response may be to make keywords required to match, so that reading the `impl`
file would have a compiled guarantee of correctness, avoiding confusion.
However, consider a similar example:
- `api` file:
```
class Foo {
fn Bar();
private fn Wiz();
};
```
- `impl` file:
```
fn Foo.Bar() { ...impl... }
private fn Foo.Wiz() { ...impl... }
fn Baz() { ...impl... }
```
In this example, `Foo.Bar` is public, and that may lead developers to conclude
that `Baz` is public. This could be corrected by requiring `private` on `Baz`,
but we are hesitant to do that per
[Default `impl` to public](#default-impl-to-public).
There is still some risk of confusion if the forward declaration and separate
definition are both in the `api` file. For example:
```
private fn PrintLeaves(Node node);
fn PrintNode(Node node) {
Print(node.value);
PrintLeaves(node);
);
fn PrintLeaves(Node node) {
for (Node leaf : node.leaves) {
PrintNode(leaf);
}
}
```
In this, a reader may read the `PrintLeaves` definition and incorrectly conclude
that it is implicitly `public` because (a) it has no keywords and (b) it is in
the `api` file. This will be addressed as part of
[Open question: Calling functions defined later in the same file #472](https://github.com/carbon-language/carbon-lang/issues/472#issuecomment-915407683).
Overall, the decision to _disallow_ keywords on separate definitions means that
`impl` files shouldn't have any visibility keywords at the file scope (they will
on classes), which is considered a writability improvement while keeping the
`api` as the single source of truth for `public` entities, addressing
readability.