فهرست منبع

Merging forward declarations (#3762)

- Add the `extern` keyword for forward declarations in libraries that
don't
    provide the definition.
-   Treat repeated forward declarations as redundant.
    -   Allow them when they prevent a dependence on an imported name.
- Clarify rules for when modifier keywords should be on forward
declarations
    and definitions.

---------

Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
Jon Ross-Perkins 2 سال پیش
والد
کامیت
aacf0cd576
1فایلهای تغییر یافته به همراه876 افزوده شده و 0 حذف شده
  1. 876 0
      proposals/p3762.md

+ 876 - 0
proposals/p3762.md

@@ -0,0 +1,876 @@
+# Merging forward declarations
+
+<!--
+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/3762)
+
+<!-- toc -->
+
+## Table of contents
+
+-   [Abstract](#abstract)
+-   [Problem](#problem)
+-   [Background](#background)
+    -   [Prior discussion](#prior-discussion)
+    -   [Forward declarations in current design](#forward-declarations-in-current-design)
+    -   [ODR (One definition rule)](#odr-one-definition-rule)
+    -   [Requiring matching declarations to merge](#requiring-matching-declarations-to-merge)
+-   [Proposal](#proposal)
+-   [Details](#details)
+    -   [`extern` keyword](#extern-keyword)
+    -   [When declarations are allowed](#when-declarations-are-allowed)
+        -   [No forward declarations after declarations](#no-forward-declarations-after-declarations)
+        -   [Files must either use an imported declaration or declare their own](#files-must-either-use-an-imported-declaration-or-declare-their-own)
+        -   [Libraries cannot both define an entity and declare it `extern`](#libraries-cannot-both-define-an-entity-and-declare-it-extern)
+        -   [`impl` files with a forward declaration must contain the definition](#impl-files-with-a-forward-declaration-must-contain-the-definition)
+        -   [Type scopes may contain both a forward declaration and definition](#type-scopes-may-contain-both-a-forward-declaration-and-definition)
+    -   [Using `extern` declarations in `extern impl`](#using-extern-declarations-in-extern-impl)
+    -   [`impl` lookup involving `extern` types](#impl-lookup-involving-extern-types)
+    -   [Modifier keywords](#modifier-keywords)
+-   [Rationale](#rationale)
+-   [Alternatives considered](#alternatives-considered)
+    -   [Other modifier keyword merging approaches](#other-modifier-keyword-merging-approaches)
+    -   [No `extern` keyword](#no-extern-keyword)
+    -   [Looser restrictions on declarations](#looser-restrictions-on-declarations)
+    -   [`extern` naming](#extern-naming)
+    -   [Default `extern` to private](#default-extern-to-private)
+    -   [Opaque types](#opaque-types)
+    -   [Require a library provide its own `extern` declarations](#require-a-library-provide-its-own-extern-declarations)
+    -   [Allow cross-package `extern` declarations](#allow-cross-package-extern-declarations)
+-   [Appendix](#appendix)
+    -   [Migration of C++ forward declarations](#migration-of-c-forward-declarations)
+
+<!-- tocstop -->
+
+## Abstract
+
+-   Add the `extern` keyword for forward declarations in libraries that don't
+    provide the definition.
+-   Treat repeated forward declarations as redundant.
+    -   Allow them when they prevent a dependence on an imported name.
+-   Clarify rules for when modifier keywords should be on forward declarations
+    and definitions.
+
+## Problem
+
+A forward declaration can be merged with a definition when they match. However,
+there is ambiguity about behavior:
+
+-   Whether a forward declaration can be repeated, including after a definition.
+-   Whether a keyword is required to make a forward declaration separate from
+    the implementing library.
+-   Whether modifier keywords need to match between forward declarations and
+    definitions.
+
+## Background
+
+### Prior discussion
+
+-   [Proposal #1084: Generics details 9: forward declarations](https://github.com/carbon-language/carbon-lang/pull/1084)
+    covered specifics for `impl` and `interface`.
+-   [Issue #1132: How do we match forward declarations with their definitions?](https://github.com/carbon-language/carbon-lang/issues/1132)
+-   [Issue #3384: are abstract / base specifiers permitted or required on class forward declarations?](https://github.com/carbon-language/carbon-lang/issues/3384)
+    asks one question about which keywords exist on declarations versus
+    definitions. There has also been discussion about what happens for member
+    functions and other situations. At present there is no decision on #3384.
+-   The
+    [2023-11-21 toolchain meeting](https://docs.google.com/document/d/1s3mMCupmuSpWOFJGnvjoElcBIe2aoaysTIdyczvKX84/edit?resourcekey=0-G095Wc3sR6pW1hLJbGgE0g&tab=t.0#heading=h.y377x4v44h2c)
+    had some early discussion regarding forward declarations and the need for a
+    syntactic difference, which evolved to `extern` here.
+-   [On Discord's #syntax on 2024-01-29](https://discord.com/channels/655572317891461132/709488742942900284/1201693766449102878),
+    there was discussion about the question "What is the intended interaction
+    between declaration modifiers and qualified declaration names?"
+-   [On Discord's #syntax on 2024-02-23](https://discord.com/channels/655572317891461132/709488742942900284/1210694116955000932),
+    there was discussion that started about how `alias` would resolve forward
+    declaration conflicts. This continued to forward declarations in general,
+    and has discussion about `extern` use.
+    -   This discussion led to a substantial fraction of this proposal.
+
+### Forward declarations in current design
+
+Example rules for forward declarations in the current design:
+
+-   [High-level](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/README.md#declarations-definitions-and-scopes)
+-   [Classes](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/classes.md#forward-declaration)
+-   [Functions](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/functions.md#function-declarations)
+-   Generics:
+    -   [`impl`](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/generics/details.md#forward-impl-declaration)
+    -   [`interface`](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/generics/details.md#declaring-interfaces-and-named-constraints)
+-   [Matching and agreeing](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/generics/details.md#matching-and-agreeing)
+
+### ODR (One definition rule)
+
+[C++'s ODR](https://en.cppreference.com/w/cpp/language/definition) requires each
+entity to have only one definition. C++ has trouble detecting issues at compile
+time, and linking has trouble catching linker issues too. ODR violation
+detection is an active problem
+(https://maskray.me/blog/2022-11-13-odr-violation-detection).
+
+This similarly applies to Carbon. In Carbon, only one library can define an
+entity; other libraries can make forward declarations of it, but those don't
+constitute a definition. Part of the goal in declaration syntax (`extern` in
+particular) is to be able to better diagnose ODR violations based on
+declarations.
+
+### Requiring matching declarations to merge
+
+When two declarations have the same name, either within the same file or through
+imports, they need to "match", or will be diagnosed as a conflict. This is laid
+out at under
+[Matching and agreeing](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/generics/details.md#matching-and-agreeing)
+in generics. This proposal does not significantly affect these type and name
+restrictions.
+
+For example:
+
+-   `fn F(); fn F() {}` could merge because the function signatures match,
+    forward declaring a function is valid, and only one definition is provided.
+-   `fn F(); fn F(x: i32);` won't merge because they differ in parameters,
+    resulting in a diagnostic.
+
+## Proposal
+
+1.  Add `extern` declarations.
+    1.  `extern` is used to mark forward declarations of entities defined in a
+        different library.
+        -   The defining library must declare the entity as public in an `api`
+            file.
+        -   It is invalid for the defining library to declare as `extern`.
+        -   `extern` must only be used for declarations in libraries that don't
+            contain the definition.
+    2.  In modifiers, `extern` comes immediately after access control keywords,
+        and before other keywords.
+    3.  Declaring an entity as `extern` anywhere in a file means it must be used
+        as `extern` _throughout_ that file.
+        -   The entity cannot be used _at all_ prior to the `extern`
+            declaration, even if there is an imported declaration.
+        -   This allows refactoring to add and remove declarations without
+            impacting files that have an `extern`.
+    4.  If a library declares an entity as non-`extern` in either the `api` or
+        `impl`, it is invalid to declare the same entity as `extern` elsewhere
+        within the library.
+2.  An entity may only be forward declared once in a given file. A forward
+    declaration is only allowed before a definition.
+3.  Modifier keywords will be handled on a case-by-case basis for merging
+    declarations.
+
+## Details
+
+### `extern` keyword
+
+An `extern` modifier keyword is added. It modifies a forward declaration to
+indicate that the entity is not defined by the declaring library. A library
+using `extern` to declare an entity cannot define that entity. Only the library
+which defines an entity can omit `extern`, and omitting `extern` means it must
+provide a definition.
+
+An `extern` declaration forms an entity which has no definition or storage. For
+example:
+
+-   `extern fn` forms a function that can be called.
+-   `extern class` forms an incomplete type.
+-   `extern interface` forms an undefined interface.
+-   `extern var` or `extern let` will bind the name without allocating storage.
+    Initializers are disallowed.
+
+The `extern` keyword is invalid on declarations that have _only_ a declaration
+syntax and lack storage, such as `alias` or `namespace`. It is only valid on
+namespace-scoped names; it is invalid on type-scoped names
+(`class Foo { extern fn Member(); }`).
+
+In declaration modifiers, `extern` comes immediately after access control
+keywords, and before other keywords. For example,
+`private extern <other modifiers> class B;`. At present, when `extern` is on a
+declaration, only access modifiers are valid (see
+[Modifier keywords](#modifier-keywords)).
+
+### When declarations are allowed
+
+When considering whether a declaration is allowed, we apply the rules:
+
+1. A declaration should always add new information.
+    - No declarations after a definition.
+2. Only one library can declare an entity without `extern`.
+3. Support moving declarations between already-imported `api` files without
+   affecting compilation of client libraries.
+
+#### No forward declarations after declarations
+
+In a file, a forward declaration must never follow a forward declaration or
+definition for the same entity.
+
+For example:
+
+```carbon
+class A { ... }
+// Invalid: Disallowed after the definition.
+class A;
+
+class B;
+// Invalid: Disallowed due to repetition.
+class B;
+
+class C;
+// Valid: Allowed because the definition is added.
+class C { ... }
+```
+
+#### Files must either use an imported declaration or declare their own
+
+In a file, if a declaration or definition of an entity is imported, the file
+must choose between either using that version or declaring its own. It cannot do
+both.
+
+For example:
+
+```carbon
+package Foo library "a" api;
+
+class C { ... }
+```
+
+```carbon
+package Foo library "b" api;
+import library "a";
+
+extern class C;
+
+// Valid: Uses the incomplete type of the extern declaration.
+fn Foo(c: C*);
+```
+
+```carbon
+package Foo library "c" api;
+import library "a";
+
+// Valid: Uses the complete type of the imported definition.
+fn Foo(c: C);
+```
+
+```carbon
+package Foo library "d" api;
+import library "a";
+
+fn Foo(c: C);
+
+// Invalid: `Foo` used the imported `C`, so an `extern` declaration of `C` is now
+// invalid.
+extern class C;
+```
+
+#### Libraries cannot both define an entity and declare it `extern`
+
+In a library, if the `impl` defines an entity, the `api` must not use `extern`
+when declaring it.
+
+For example:
+
+```carbon
+package Wiz library "a" api;
+
+extern class C;
+```
+
+```carbon
+package Wiz library "a" impl;
+
+// Invalid: The `api` file declared `C` as `extern`.
+class C { ... }
+```
+
+In a library, the `api` might make an `extern` declaration that the `impl`
+imports and uses the definition of. This is consistent because the `impl` file
+is not declaring the entity.
+
+#### `impl` files with a forward declaration must contain the definition
+
+In an `impl` file, if the `impl` file forward declares an entity, it must also
+provide the definition. In libraries with multiple `impl` files, this means that
+using an entity in one `impl` file when it's defined in a different `impl` file
+requires a (possibly `private`) forward declaration in the `api` file. An
+`extern` declaration cannot be used for this purpose because the library defines
+the entity.
+
+This allows Carbon to provide a compile-time diagnostic if an entity declared in
+the `impl` is not defined locally. Note that an entity declared in the `api` may
+still not get a compile-time diagnostic unless the compiler is told it's seeing
+_all_ available `impl` files.
+
+For example:
+
+```carbon
+package Bar library "a" api;
+
+class C;
+
+class D { ... }
+```
+
+```carbon
+package Bar library "a" impl;
+
+// Invalid: Missing a definition in the `impl` file, but if one were added, then
+// this would be valid.
+class C;
+
+// Invalid: The `api` defines `D`. As a consequence, there is no way to make
+// this forward declaration valid.
+class D;
+```
+
+This doesn't prevent `impl` from providing a forward declaration. It might when
+it also provides the definition, which can be useful to unravel dependency
+cycles:
+
+```carbon
+package Bar library "a" api;
+
+class D;
+```
+
+```
+package Bar library "a" impl;
+
+class D;
+
+class E {
+  fn F[self: Self](d: D*) { ... }
+}
+
+class D {
+  fn G[self: Self](e: E) { ... }
+}
+```
+
+Here, the `impl` could not use the imported forward declaration in the `api`
+because of the rule
+[Files must either use an imported declaration or declare their own](#files-must-either-use-an-imported-declaration-or-declare-their-own).
+Without `class D;` present, the definition of `F` would be invalid.
+
+#### Type scopes may contain both a forward declaration and definition
+
+The combination of a forward declaration and a definition is allowed in type
+scopes.
+
+For example:
+
+```carbon
+class C {
+  class D;
+
+  fn F() -> D;
+
+  class D {
+    fn G() -> Self { return C.F(); }
+
+    var x: i32;
+  }
+
+  fn F() -> D { return {.x = 42}; }
+}
+```
+
+This is necessary because type bodies are not automatically moved out-of-line,
+unlike function bodies.
+
+### Using `extern` declarations in `extern impl`
+
+It is invalid for a non-`extern` `impl` declaration to use an `extern` type in
+its type structure.
+
+Consider two libraries, one defining `A` and declaring `B` as `extern`, and the
+other defining `B` and declaring `A` as `extern`. Neither should be able to
+define an `impl` involving both `A` and `B`, otherwise both could.
+
+### `impl` lookup involving `extern` types
+
+If `impl` lookup involving `extern` types finds a non-`final` parameterized
+`impl`, the result is that the lookup succeeds, but none of the values of the
+associated entities of interface are known. This is because there may be another
+more specialized `impl` that applies that is not visible (as can also happen
+with constrained generics).
+
+For example:
+
+```
+library "a" api;
+extern class C;
+extern class D(T:! type);
+extern impl forall [T:! type] D(T) as I where .Result = i32;
+```
+
+In the above, `D(C)` impls `I`, but with unknown `.Result`, since it might not
+be `i32`.
+
+### Modifier keywords
+
+When considering various modifiers on a forward declaration versus definition:
+
+-   `extern` is only valid on a forward declaration. Rules are detailed above.
+-   `extend` in `extend impl` is only on the declaration in the class body
+    (whether that is a forward declaration or definition), as described at
+    [Forward `impl` declaration](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/generics/details.md#forward-impl-declaration).
+-   Other class, impl, and interface modifiers (`abstract`, `base`, `final`)
+    exist only on the definition, not on the forward declaration.
+-   Function modifiers (`impl`, `virtual`, `default`, `abstract`, `final`) must
+    match between forward declaration and definition.
+    -   This only affects type-scoped names because they are invalid on
+        namespace names.
+    -   `abstract` won't have a definition so is moot here.
+-   Access modifiers (`private` and `protected`) must match.
+    -   As an exception, an `extern` name may be `private` when the actual name
+        is public.
+        -   This allows a library to forward declare another library's type
+            without allowing clients to depend on its forward declaration.
+        -   On merging, the more public declaration will take precedence, hiding
+            `private extern` declarations.
+    -   This affects both type-scoped names and namespace names.
+
+## Rationale
+
+-   [Software and language evolution](/docs/project/goals.md#software-and-language-evolution)
+    -   This proposal supports moving classes between libraries without
+        affecting the compilation of clients.
+        -   Allowing a redundant `extern` declaration when a non-`extern`
+            declaration is imported allows _adding_ the class to an
+            already-imported library where `extern` declarations were previously
+            present.
+        -   Requiring the use of a local `extern` declaration prevents
+            accidental uses that might hinder _removing_ the class from an
+            imported library.
+    -   Requiring keywords on `extern` declarations only when they affect
+        calling conventions means that, in most cases, keywords can be added and
+        removed from declarations in the defining library without breaking
+        `extern` declarations in other libraries.
+-   [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write)
+    -   Explicitly flagging `extern` will assist readers in understanding when a
+        library is working with a type in order to avoid dependency loops, with
+        minimal impact on writing code.
+    -   Preventing redundant forward declaration removes a potential avenue for
+        confusion by making the meaning of entities clearer.
+    -   Requiring keywords be repeated for type-scoped functions is intended to
+        improve readability.
+-   [Practical safety and testing mechanisms](/docs/project/goals.md#practical-safety-and-testing-mechanisms)
+    -   Requiring `extern` declarations be clearly marked should improve our
+        ability to diagnose ODR violations. This will help developers by
+        improving detection of a subtle correctness issue.
+-   [Fast and scalable development](/docs/project/goals.md#fast-and-scalable-development)
+    -   `extern` declarations are considered essential to supporting separate
+        compilation of libraries, which in turn supports scaling compilation.
+-   [Interoperability with and migration from existing C++ code](/docs/project/goals.md#interoperability-with-and-migration-from-existing-c-code)
+    -   `extern` is chosen for consistency with C++, and carries a similar --
+        albeit slightly different -- meaning.
+-   [Principle: Prefer providing only one way to do a given thing](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/project/principles/one_way.md)
+    -   Setting _requirements_ for whether a keyword belongs on a forward
+        declaration or with the definition, instead of making places _optional_,
+        supports developers making conclusions based on which keywords they see
+        -- either by presence or absence.
+
+## Alternatives considered
+
+### Other modifier keyword merging approaches
+
+There has been intermittent discussion about which modifiers to allow or require
+on forward declarations versus definitions. There are advantages and
+disadvantages about redundancy and being able to copy-paste declarations. There
+might be strict requirements for some modifiers to be present in order to
+correctly use a forward declaration.
+
+This proposal suggests a partial decision here, at least for a reasonable
+starting point that we can implement towards. This will likely evolve in future
+proposals, particularly as more keywords are added. However, this still offers a
+baseline.
+
+The trade-offs we consider are:
+
+-   Consistency in when a modifier keyword is expected (if applicable) is
+    valuable.
+    -   Adding a keyword to an entity with a separate definition may require
+        adding the keyword to the forward declaration, the separate definition,
+        or both. It is disallowed where not required to be added.
+        -   For example, `base` is added to `class C { ... }`, and disallowed on
+            `class C;`.
+    -   Although we could make keywords optional where it would not affect
+        semantics, we prefer for the presence or absence of a keyword to carry a
+        clear meaning.
+        -   For example, if `base` were optional to allow `base class C;`, then
+            an adjacent `class D;` lends itself to being incorrectly interpreted
+            as meaning "`D` is not a base class" when it actually means "`D` may
+            or may not be a base class".
+-   Access control has a certain necessity for consistency, so that a consumer
+    of a forward declaration would still be allowed to use the definition if
+    refactored.
+    -   We could require similar consistency on `extern`, and should have more
+        nuanced rules if the access control rules go beyond public and
+        `private`. For example, a package-private type shouldn't be allowed to
+        be made public through an `extern` declaration; but package-private
+        isn't actually part of the language right now.
+-   For a type, we are choosing to have minimal modifiers on the declaration.
+    -   The modifiers we disallow (`abstract`, `base`, `final`, and `default`)
+        have no effect on uses because the forward declared type is incomplete.
+        -   Requiring them would end up leaking an implementation detail and
+            create toil.
+        -   Requiring them would be somewhat inconsistent with C++.
+    -   `extern` is a special-case where its presence is intrinsic to the
+        keyword's semantics.
+-   For type-scoped members, we are choosing to duplicate modifiers between the
+    declaration and definition.
+
+    -   For example:
+
+        ```
+        class A {
+          private class B;
+        }
+        // `private` is required here.
+        private class A.B { ... }
+        ```
+
+    -   There is more emphasis on being able to copy-paste a function
+        declaration. This in particular may help developers more than classes
+        because all the parameters must also be copied. Things such as `static`
+        in C++ being declaration-only is also something we view as a source of
+        friction rather than a benefit.
+    -   Modifiers such as `virtual` affect the calling convention of functions,
+        and as a consequence _must_ be on the first declaration.
+    -   A downside of this approach is that it means the class name is inserted
+        in the middle of the out-of-scope definition, rather than near the
+        front.
+
+The most likely alternative would be to disallow most modifiers on out-of-line
+definitions after a forward declaration, for type member functions in specific.
+This would be because, unlike other situations, a member must have a forward
+declaration if there is an out-of-line definition. We would probably want to
+drop these collectively in order to maximize copy-paste ability (ideally,
+everything before `fn` is dropped). However, it would shift understandability of
+the definition in a way that may be harmful. For now this proposal suggests
+adopting the more verbose approach and seeing how it goes.
+
+Another alternative is that we could allow flexibility to choose which modifier
+keywords are provided where. For example, keywords on a function definition must
+be some subset of the keywords on the declaration; keywords on a type
+declaration must be some subset of the keywords on the definition. This would
+allow authors to choose when they expect keywords will be most relevant.
+However, it could also serve to detract from readability: two functions in the
+same file might be declared similarly, but have different keywords on the
+definition, implying a difference in behavior that would not exist. With
+consideration for the
+[Principle: Prefer providing only one way to do a given thing](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/project/principles/one_way.md),
+this proposal takes the more prescriptive approach, rather than offering
+flexibility.
+
+Note a common aspect between types and functions in the proposed model is that
+modifiers on the definition are typically a superset of modifiers on the
+declaration (`extern` as an exception). While looking at the declaration always
+gives an incomplete view, looking at the definition can give a complete view.
+
+### No `extern` keyword
+
+If we had no `extern` keyword, then a declaration wouldn't give any hint of
+whether a library contains the definition. Given two forward declarations in
+different `api` files, either both or neither could have a definition in their
+`impl` file. Sometimes this would be detected during linking, particularly if
+both are linked together. However, providing an `extern` keyword gives a hint
+about the intended behavior, allowing us to evaluate more cases for warnings. It
+also gives a hint to the reader, about whether an entity is expected to be
+declared later in the library (even if not in the same file).
+
+For example:
+
+```carbon
+package Foo library "a" api;
+
+class C;
+```
+
+```carbon
+package Foo library "a" impl;
+
+class C {}
+```
+
+```carbon
+package Foo library "b" api;
+import library "a";
+
+class C {};
+```
+
+Without the `extern` keyword, this code should be expected to compile. Ideally
+it would be caught during linking that there are two definitions of `class C`,
+but that relies on some tricks to catch issues. When `extern` is added, then the
+processing of library "b" results in a conflict between the `class C;` forward
+declared in the api of library "a".
+
+Note this probably does not fundamentally alter the amount that can be
+diagnosed, but will mainly allow some diagnostics to occur during compilation
+that otherwise would either be linker diagnostics or missed.
+
+The `extern` keyword is being added mainly for diagnostics and readability.
+
+### Looser restrictions on declarations
+
+We are being restrictive with declarations and when they're allowed. Primarily,
+we want to avoid confusion with code such as:
+
+```carbon
+class C { ... }
+
+// This declaration has no effect.
+class C;
+```
+
+But, we could also allow code such as:
+
+```carbon
+package Foo library "a" api;
+
+class C { ... }
+```
+
+```carbon
+package Foo library "b" api;
+import library "a";
+
+fn F(c: C) { ... }
+
+extern class C;
+
+fn G(c: C*) { ... }
+```
+
+Here, `F` requires the imported definition of `C`. But, is `G` seeing an
+incomplete type from the `extern`, or is the `extern` redundant and `G` sees the
+imported definition of `C`? Would a library importing library "b" see `C` as an
+incomplete type, or a complete type?
+
+In order to eliminate potential understandability issues with the choices we may
+make, we are choosing the more restrictive approaches which disallow both of
+these. In the first case, the redeclaration after a definition in the same file
+is simply disallowed. In the second case, library "b" cannot declare `C` as
+`extern` after using the imported definition. Restrictions such as these should
+make code clearer by helping developers catch redundant, and possibly incorrect,
+code.
+
+### `extern` naming
+
+Beyond `extern`, we also considered `external`. `extern` implies external
+linkage in C++. We're choosing `extern` mainly for the small consistency with
+C++.
+
+### Default `extern` to private
+
+The `extern` keyword could have an access control implication equivalent to
+`private`. Then `extern` would need explicit work to export the symbol. The
+`export` keyword was proposed for this purpose, with the idea that
+`export import` syntax might also be provided to re-export all symbols of an
+imported library.
+
+1.  `extern` and `export` semantic consistency
+
+    This would mean that `extern` declarations have different access control
+    than other declarations, which is a different visibility model to
+    understand, and may also not be intuitive from the "external" meaning. The
+    use of `export` matches C++'s `export` keyword, which may also imply to
+    developers that other semantics match C++, such as `export` being necessary
+    for all declared names.
+
+2.  Interaction with additional access control features
+
+    We'll probably also want more granular access control than just public and
+    private for API names. Adding package-private access modifier seems useful
+    (for example, Java provides this as `package`): in C++, this is sometimes
+    achieved through an "internal" or "details" namespace. If Carbon only
+    supports library-private symbols, that still addresses some of these
+    use-cases, but will sometimes require private implementation details to
+    exist in a single `api` file in order to get language-supported visibility
+    restrictions. In some cases this will result in an unwieldy amount of code
+    for a single file.
+
+    For example of how package-private visibility might be used, gtest has
+    [an internal header directory](https://github.com/google/googletest/blob/main/googletest/include/gtest/internal)
+    that contains thousands of lines of code. If this needed to migrate to `api`
+    files in Carbon, it would be ideal if users could not access the names by
+    importing an internal library.
+
+    For example of how this would interact:
+
+    | Default visibility             | Public (proposed)         | Private (alternative)            |
+    | ------------------------------ | ------------------------- | -------------------------------- |
+    | Public                         | `extern class C;`         | `export extern class C;`         |
+    | Library-private                | `private extern class C;` | `extern class C;`                |
+    | Package-private (hypothetical) | `package extern class C;` | `export package extern class C;` |
+
+3.  Risks of a public extern
+
+    When making an `extern fn`, it is callable. This creates a risk of a
+    function being declared as `extern` for internal use, but accidentally
+    allowing dependencies on the function. This risk could be mitigated by
+    requiring access control of an `extern` to be either equal to or more
+    restrictive than the original symbol (which might be hard to validate, but
+    could be validated when both symbols are seen together).
+
+    When making an `extern class`, it's an incomplete type. It cannot be
+    instantiated, but pointers and references may be declared. This is only
+    really useful if there are functions which take a pointer as a parameter,
+    but an instance of the pointer could only be created by either unsafe casts
+    or if there's a function that returns the pointer type.
+
+    Unsafe casts carry an inherent risk. In the case of a returned pointer, that
+    type could be captured by `auto`. Having `extern class` default to private
+    does not prevent the type's use.
+
+Considering the trade-offs involved combination of these three points, this
+proposal suggests using the regular access control semantic. That choice may be
+reconsidered later, based on how the semantics work out and any issues that
+arise, particularly around access control.
+
+### Opaque types
+
+Omitting the `extern` modifier means a definition is required in a library.
+There may be a use-case for opaque types which are "owned" by a library and have
+no definition. If so, there are possible solutions such as a modifier keyword to
+indicate an opaque type. For now, an empty definition in the `impl` file of a
+library should have a similar effect: `api` users would not be able to provide a
+definition.
+
+For example:
+
+```carbon
+package Foo library "a" api;
+
+// An opaque type which can be imported by other libraries.
+class C;
+```
+
+```carbon
+package Foo library "a" impl;
+
+// An empty definition. This could be in its own file, or at the end after logic,
+// to prevent misuse.
+class C {}
+```
+
+This proposal requires a definition to exist. That choice may be reconsidered
+later, based on use-cases.
+
+### Require a library provide its own `extern` declarations
+
+As proposed, any library can provide `extern` declarations for other libraries
+in the same package. It was proposed that this should be restricted so that a
+library would need to make `extern` declarations available, either through a
+separate `extern` file (similar to `api` and `impl`) or through additional
+markup in the `api` file which could be used to automatically generate an
+`extern`-only subset of the `api` (still requiring entities which _should_ be
+`extern` to be explicitly marked).
+
+Advantages:
+
+-   Centralizes ownership of `extern` declarations.
+    -   We are already planning to require a package to provide `extern`
+        declarations. This goes a step further, requiring the `extern`
+        declaration be provided by the same library that's defining the entity,
+        providing a clear, central ownership.
+-   Simplifies refactoring.
+    -   The current plan of record is to require that `extern` declarations
+        agree with the library's declaration of the definition. This includes
+        small details such as parameter names. A consequence of this is that
+        changing these details in one declaration requires changing them in
+        _all_ declarations, atomically. There's a desire to limit the scope of
+        atomic refactorings; for example, similar package-scope atomic
+        refactoring requirements lead us to _allow_ certain redundant forward
+        declarations elsewhere in this proposal.
+-   Reduces complexity for migrating C++ forward declarations.
+    -   As described in
+        [Migration of C++ forward declarations](#migration-of-c-forward-declarations),
+        we expect migrating forward declarations to be difficult. Several of the
+        steps needed already can produce results similar to this alternative.
+        Under this alternative, we would make the handling of an entity defined
+        in a different library identical to as if it were in a different
+        package: a reduction of one case.
+
+Disadvantages:
+
+-   Makes it difficult for libraries which want to use `extern` declarations to
+    use minimal imports for a declaration.
+    -   If a library provides multiple `extern` declarations, the `extern`
+        file's imports would be a superset of the imports for those
+        declarations. If a client library only wants one of those `extern`
+        declarations, it would still get the full set of dependencies; under the
+        proposed syntax, only the single declaration's dependencies are
+        required. This allows for fewer dependencies.
+-   Does not support use-cases that may occur in C++.
+    -   It's possible that an extern declaration may depend on a defined entity,
+        where that entity is being defined in the same file. For example,
+        `class C { ... }; extern fn F(c: C);`. Under the proposed syntax, this
+        is supported; under the alternative syntax, another solution would need
+        to be found. There are potential ways to fix this through refactoring,
+        including moving one entity to a different library, changing the
+        `extern` declaration to use only `extern` declarations, or using generic
+        programming to create an indirection. However, each of these is a
+        refactoring that may hinder adoption.
+
+At present, the disadvantages are considered to outweigh the advantages. It's
+possible that this may be revisited later if we get more code and libraries
+using these tools and recognize some patterns that we can better or more
+directly support. However, we should be hesitant to provide a second syntax for
+`extern` if it's not adding substantial value, under
+[Principle: Prefer providing only one way to do a given thing](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/project/principles/one_way.md).
+
+### Allow cross-package `extern` declarations
+
+We could choose a syntax for `extern` declarations that allows cross-package
+`extern` declarations. These are effectively supported in C++, where there are
+no package boundaries. Dropping support will create a migration barrier.
+
+However, there is a strong desire to restrict the use of cross-package
+declarations in order to reduce the difficult and complex refactoring costs that
+result from cross-package declarations: requiring that a particular name not
+change its declaration category (a `class` must remain a `class`, preventing
+`alias`) and that parameters (either function or generic) must remain the same,
+not even allowing implicit conversions.
+
+The package boundaries serve an important purpose in balancing costs for
+refactoring.
+
+## Appendix
+
+### Migration of C++ forward declarations
+
+This is not proposing a particular approach to migration. However, for
+consideration of the proposal, it can be helpful to consider how migration will
+work.
+
+It's expected under this approach that migration of a forward declaration will
+require identifying the Carbon library that defines the entity. Then:
+
+-   The forward declaration will need to be adjusted based on library and
+    package boundaries:
+    -   If the forward declaration is disallowed in Carbon, it may need to be
+        removed.
+    -   If the forward declaration is in the same library as the defining
+        library, then no `extern` is required.
+    -   If the forward declaration is in a different library but the same
+        package, then `extern` is added.
+    -   If the forward declaration is in a different package, the forward
+        declaration must be removed. To replace it, there are a couple options
+        which would need to be chosen by heuristic:
+        1.  Add a dependency on the actual definition. This might be infeasible
+            when the defining library has many complex dependencies.
+        2.  Add a library to the other package that provides the necessary
+            `extern` declaration. This might be infeasible when the package is
+            not owned by the package being migrated.
+    -   If the forward declared code is in C++, we need to retain a forward
+        declaration in C++. A couple examples of how we might achieve that are:
+        -   Provide Carbon syntax for an in-file forward declaration of C++ code
+            (for example, `extern cpp <declaration>`).
+        -   Create a small C++ header providing the forward declaration, and
+            depend on it.
+-   Fix meaningful differences in parameter names, for example by updating the
+    forward declaration's parameter names to match the definition.
+-   Fix meaningful differences in modifier keywords, for example by adding a
+    function forward declaration's modifiers to the definition.