In order to support exporting imported names, add
export import library <library>
and export <name reference>
syntax.
As we develop libraries such as the prelude, we want the ability to indicate
that an imported name should be re-exported for indirect use. At present, we can
use the prototype alias
to expose names on a case-by-case basis
(alias Foo = Bar;
), but it doesn't work to export the same name
(alias Foo = Foo;
is a name conflict), and we want to be able to more broadly
forward a library's exported names.
For example:
package Foo library "internal";
// Declare C.
class C;
package Foo;
// We want the ability to expose everything imported here.
import library "internal";
import library Foo;
// Uses C by way of the default library.
var c: Foo.C;
Some of the syntax options were discussed on Discord.
Names declared in a Carbon file are currently exported by default. A private
keyword may be used to prevent that export. Note C++ and TypeScript use
private-by-default behavior, so the syntax choices that make sense elsewhere may
not make as much sense for Carbon.
As described in the problem statement, alias
offers incomplete re-export
support. However, alias
is not fully designed; it's modeled on informal
discussions.
In C++ modules, this is
export import ...
.
In TypeScript modules, similar syntax might look like:
import * from '<module>'
export * from '<module>'
In Python, names in a module are generally public, and imported names are
accessible too. For example, given import datetime
, the module makes the name
datetime
available to clients. There is interest in
more explicit export
syntax.
In other languages, such as Java, Kotlin, or Go, direct re-exports aren't supported. Instead, the expectation seems to be that either a copy of the entity would be exported, or it should just be moved.
Support the export
keyword as a modifier to import library <library>
(excluding cross-package imports). This is export import
for short. For
example:
export import library "lib";
Additionally, support the export
keyword on individual, file-scoped or
namespace-scoped entities (excluding entities in other packages, and namespaces
themselves). This is export name
for short. For example:
// Export an entity:
export Foo;
// Export an entity inside a namespace:
export NS.Bar;
// Invalid: exporting namespaces is disallowed.
export NS;
Although exporting cross-package names is disallowed, note that alias
can be
used to add a package-local name that originates from another package, which
then is valid for export. For example:
import package Other;
// Invalid: cross-package exports are disallowed.
export Other.Obj;
// This introduces a package-local name. The alias is exported, and other
// libraries importing this library may export `Obj`.
alias Obj = Other.Obj;
The export
keyword is only valid in files which are valid to import. It is
invalid in files which cannot be imported: implementation files and
Main//default
.
In the
source file introduction,
export import
directives are intermingled with other import
directives.
export name
directives are normal code and cannot be intermingled with any
import
directives, including export import
directives.
This allows:
import library "foo";
export import library "wiz";
import library "bar";
export FooType;
class C { ... };
export BarType;
This disallows:
import library "foo";
// Invalid: All `import` directives must come before other code, including
// `export name`.
export FooType;
import library "bar";
class C { ... };
// Invalid: `export import` must be grouped with `import` directives.
export import library "wiz";
Namespaces are not valid arguments to export
; entities in namespaces must be
individually exported.
This keeps open a future design option of having export
on a namespace export
all imported names inside the namespace, such as export NS;
. This could also
be achieved with *
syntax, such as export NS.*;
. There hasn't been
discussion of this option, and this proposal takes no stance on the option.
- Software and language evolution
export <name>
allows moving entities between libraries without needing to make modifications to clients, enabling more incremental refactorings.
- Code that is easy to read, understand, and write
- Export logic in general is intended to support factoring large or
complex APIs into multiple, smaller files. For example, with the
prelude, we'll have many types, interfaces, and implementations: concise
re-exporting logic will make it easy to provide a singular
prelude.carbon
that exports all related functionality.
- Export logic in general is intended to support factoring large or
complex APIs into multiple, smaller files. For example, with the
prelude, we'll have many types, interfaces, and implementations: concise
re-exporting logic will make it easy to provide a singular
We discussed several different syntax choices.
A couple placement alternatives discussed were:
- Put
export
beforelibrary
. For example,import export library "lib"
.- An advantage of this is that if we support cross-package re-exports,
import Foo export library "lib"
could make it clearer the library is being re-exported, rather than the package. - A disadvantage is that we would probably not put other keywords between the package and library.
- An advantage of this is that if we support cross-package re-exports,
- Put
export
as a suffix. For example,import library "lib" export
.- An advantage of this is that it makes
import
statements line up better when some may not have theexport
modifier.
- An advantage of this is that it makes
The current design uses export
as a prefix. This is for consistency with how
we put other modifier keywords, such as private
or extern
, prior to the
introducer keyword.
A couple keyword alternatives discussed (alongside placement options) were:
reexport
- An advantage noted is that it may read more intuitively for some developers.
- This proposal suggests
export
because it mirrorsimport
, and it's consistent with multiple other languages. It's also shorter, and Carbon often chooses keywords for shortness.
exported
andreexported
- These didn't seem to read as clearly as
export
orreexport
.
- These didn't seem to read as clearly as
We see several options for export name
placement. This compares them, focusing
on advantages and disadvantages for each option.
-
export name
withimport
sexport name
can (only) appear in the preamble, with the imports, and cannot appear with the other declarations in the library. Note this option could either haveexport name
refer to earlierimport
s (creating an ordering consistency issue), or expend additional effort in order to track whether a name was already imported at the site of theexport
.Advantages:
- No need to teach developers they cannot (don't need to)
export
locally introduced names.
Disadvantages:
- Although the restricted placement might imply placement is tied to
specific libraries, that's not the case. This could mislead developers.
- In theory, we could enforce this, but then we could end up breaking code if the path a name is imported through changes.
- No need to teach developers they cannot (don't need to)
-
export name
with other declarationsexport name
can only appear after imports. This means that all names valid forexport
will already be made available.Advantages:
import
remains very special.- Makes it unambiguous that names valid for
export
are already imported.
Disadvantages:
- Prevents placing
export name
next to the import that is expected to add the name. - Means
export import
andexport name
will be in different sections: no single place to look for re-exports.
-
No ordering for
export name
Let developers choose what the prefer.
Advantages:
- Maximum flexibility, HOA rule.
Disadvantages:
- Most inconsistent with the desire to treat
import
as special.
We're choosing option (2). The name lookup issues avoided by requiring export
be below import
directives seem worthwhile.
A possible option to (2) might be to create an additional section dedicated to
export name
below the import
section. This proposal suggests avoiding that
in order to avoid increasing the amount of enforced ordering in Carbon files.
As proposed, re-exporting names from other packages will not be supported. This is done to continue maintaining package boundaries, and so that names aren't unexpectedly introduced. For example:
package Foo;
class C;
package Bar;
export import Foo;
package Wiz;
import Bar;
In the last package Wiz
, it might be confusing if the name Foo.C
should be
introduced: typically importing Bar
would put everything under the Bar
namespace.
We may choose to re-examine this choice, but this proposal does not include support.