|
@@ -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.
|