Question for leads
#665: private vs public syntax strategy, as well as other visibility tools like external/api/etc.
decided that methods on classes should default to public. Should api echo the
similar strategy?
- In C++,
structmembers default public, whileclassmembers defaultpublic. - In proposal
#107: Code and name organization,
an
apikeyword was used to indicate public APIs within anapifile. - In #665, it was
decided that Carbon class members should default
public.- This issue was reopened to discuss alternatives in this 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.
- 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.
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
apiandimplwithout visibility changing.
Disadvantages:
- The
apifile'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.
Noting that we default api to public, we could similarly default impl to
public.
Advantages:
- Can move functions between
apiandimplwithout visibility changing.
Disadvantages:
- Everything in an
implfile must be private unless it's a separate definition of anapideclaration. As a consequence, everything declared in theimplfile would need to be explicitlyprivate.
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.
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:
-
apifile:class Foo { private fn Bar(); private fn Wiz(); }; -
implfile: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:
-
apifile:class Foo { fn Bar(); private fn Wiz(); }; -
implfile: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.
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.
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.