diff --git a/proposals/p5168.md b/proposals/p5168.md new file mode 100644 index 000000000000..e8962c119c09 --- /dev/null +++ b/proposals/p5168.md @@ -0,0 +1,1017 @@ +# Forward `impl` declaration of an incomplete interface + + + +[Pull request](https://github.com/carbon-language/carbon-lang/pull/5168) + + + +## Table of contents + +- [Abstract](#abstract) +- [Problem](#problem) +- [Background](#background) + - [Related work and past discussion](#related-work-and-past-discussion) + - [Terminology](#terminology) + - [Other considerations](#other-considerations) +- [Proposal](#proposal) + - [Prior to this proposal](#prior-to-this-proposal) + - [Proposed rules](#proposed-rules) +- [Details](#details) + - [An `impl` implements a single interface](#an-impl-implements-a-single-interface) + - [Associated constants may be assigned in the body of an `impl` definition](#associated-constants-may-be-assigned-in-the-body-of-an-impl-definition) + - [An `impl` may be forward declared without the interface being complete](#an-impl-may-be-forward-declared-without-the-interface-being-complete) + - [Interface requirements](#interface-requirements) + - [Examples](#examples) +- [Future work](#future-work) + - [Addressing the subsumption use case previously addressed by `extend` in interfaces](#addressing-the-subsumption-use-case-previously-addressed-by-extend-in-interfaces) + - [Opt-in to using the interface's default definition of an associated function](#opt-in-to-using-the-interfaces-default-definition-of-an-associated-function) +- [Rationale](#rationale) +- [Alternatives considered](#alternatives-considered) + - [Allow implementing multiple interfaces with a single `impl` declaration](#allow-implementing-multiple-interfaces-with-a-single-impl-declaration) + - [An `impl` of an interface also implements the interfaces it extends](#an-impl-of-an-interface-also-implements-the-interfaces-it-extends) + - [Use the contents of definitions if available](#use-the-contents-of-definitions-if-available) + - [Delayed checking of incomplete types](#delayed-checking-of-incomplete-types) + - [Allow `impl` declarations with rewrites of defined but not complete interfaces](#allow-impl-declarations-with-rewrites-of-defined-but-not-complete-interfaces) + - [Can omit function declarations from `impl` body](#can-omit-function-declarations-from-impl-body) + - [Different introducer for assigning associated constants in an `impl` definition](#different-introducer-for-assigning-associated-constants-in-an-impl-definition) + + + +## Abstract + +Revise rules for what is required and provided by declarations and definitions +of interfaces and impls. In particular: + +- allow `impl` declarations of incomplete interfaces, and +- shift from a "use the information from the type definition if it happens to + be complete" model to a "only use the information from the definition in + contexts where it is required to be defined or complete" model. + +Resolves questions-for-leads issues +[#4566](https://github.com/carbon-language/carbon-lang/issues/4566), +[#4672](https://github.com/carbon-language/carbon-lang/issues/4672), +[#4579](https://github.com/carbon-language/carbon-lang/issues/4579). + +## Problem + +There are several kinds of entities in Carbon, and most of them support separate +forward declaration from definition. You might declare an entity before defining +it for a few reasons: + +- Non-generic runtime functions may be called using only a declaration in an + api file, as long as they are defined in the corresponding impl file. This + hides implementation details, and allows build work to be parallelized by + way of separate compilation. +- By + [the information accumulation principle](https://docs.carbon-lang.dev/docs/project/principles/information_accumulation.html), + we don't want code to rely on information written later in the file. For + example, names can't be used before the declaration that introduces that + name. This requires the developer to order their code in a way that + satisfies those constraints. + +For example, functions and types (classes, interfaces, named constraints, and so +on) can reference the names of any kind of type in their declaration and +definition. + +In some cases, two types will reference each other, requiring forward +declarations: + +A +[more complex example with interfaces](/docs/design/generics/details.md#example-of-declaring-interfaces-with-cyclic-references) +can be found in the generics design, that requires introducing named constraint +forward declarations to represent constraints that can't be expressed yet. + +We don't want to create a situation where Carbon is introducing a lot of +[accidental complexity](https://en.wikipedia.org/wiki/No_Silver_Bullet) into the +programming process by creating a declaration ordering puzzle that is difficult +or impossible to solve. We would like ordering rules that are straightforward to +satisfy, and simple to remember. + +There are a few different options for ordering requirements, all of which have +downsides: + +- Light requirements are easy to satisfy, but don't provide as much + information to use. For example, if you don't require that an interface is + defined at a point, then you can't use the interface requirements from its + definition. +- Strong requirements are difficult to satisfy, and in the worst case can + create dependency cycles that can't be satisfied at all. +- We could have light requirements, but then use additional information when + it is available. This creates complexity. There is complexity in the + implementation since there are many cases to consider, and if something goes + wrong it could be for many different possible reasons. It increases the + danger of introducing coherence concerns, where reordering declarations and + definitions could change the meaning of the code. This gives some + (situational) flexibility to the developer to solve ordering problems. + +The design prior to this proposal often uses this last option, creating +implementation complexity. We would like to find an alternate approach that +gives similar flexibility without the downsides. + +## Background + +### Related work and past discussion + +- [Proposal #722](https://github.com/carbon-language/carbon-lang/pull/722): + Nominal classes and methods +- [Proposal #818](https://github.com/carbon-language/carbon-lang/pull/818): + Constraints for generics (generics details 3) + - Introduced implied constraints. +- [PR #1026](https://github.com/carbon-language/carbon-lang/pull/1026): + Clarify class declaration syntax +- [Proposal #1084](https://github.com/carbon-language/carbon-lang/pull/1084): + Generics details 9: forward declarations + - See + [the section in the generic details design document](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/generics/details.md#forward-declarations-and-cyclic-references) +- [Proposal #2760](https://github.com/carbon-language/carbon-lang/pull/2760): + Consistent class and interface syntax +- [Proposal #3762](https://github.com/carbon-language/carbon-lang/pull/3762): + Merging forward declarations +- [Proposal #3763](https://github.com/carbon-language/carbon-lang/pull/3763): + Matching redeclarations + - Updated the rules of `impl` declarations and matching +- [Proposal #3980](https://github.com/carbon-language/carbon-lang/pull/3980): + Singular extern declarations +- [PR #4230](https://github.com/carbon-language/carbon-lang/pull/4230): Add + documentation for entity declaration design work +- Pending + [leads question #4566](https://github.com/carbon-language/carbon-lang/issues/4566): + Implementing multiple interfaces with a single impl definition + - The currently proposed resolution includes `impl`ing an interface does + not `impl` the interfaces it `extend`s, reducing the need to see the + `interface` definition in `impl` declarations. +- Pending + [leads question #4579](https://github.com/carbon-language/carbon-lang/issues/4579): + When are interface requirements enforced for an `impl`? + - Considers what is required and provided by `impl` forward declarations + and definitions. This proposal offers a resolution to these questions. +- Pending + [leads question #4672](https://github.com/carbon-language/carbon-lang/issues/4672): + Declaration and definition of impls and their associated functions + - The currently proposed resolution includes moving assignment of + associated constants into the `impl` definition in the normal case, + reducing the need to see the `interface` definition in `impl` + declarations. +- [Proposal #5087](https://github.com/carbon-language/carbon-lang/pull/5087): + Qualified lookup into types being defined + - Allows member access inside a type's definition, in addition to + afterwards. Introduces the _defined_ terminology. + +### Terminology + +- A _declaration_ specifies information about an entity. Declarations are + either _forward declarations_ (which generally end with a semicolon `;`) or + _definitions_ (which generally end with a body enclosed in curly braces + `{`...`}`). +- We say an entity is _declared_ by the first declaration textually in the + source. If that declaration is a definition, we say the entity is declared + at the point the body begins (at the open curly `{`). +- We say an entity is _defined_ once the body of its definition is started (at + the open curly `{`). We allow name lookup into an entity once it is defined. + See + [proposal #5087: Qualified lookup into types being defined](https://github.com/carbon-language/carbon-lang/pull/5087). +- We say an entity is _complete_ at the end of its definition (at the close + curly `}`). +- We say a facet type is _identified_ if all the interfaces it references are + declared and all of its named constraints are complete. An identified facet + type is associated with a known set of interfaces. +- An _implied constraint_ is a condition that must hold if a type is used in a + given type expression, which we enforce in some way other than checking that + the condition holds at that point. For example: + + ``` + interface Hash { ... } + class HashSet(T:! Hash) { ... } + + // `U` must satisfy the constraints on the `T` parameter to `HashSet`. + // In this case, we enforce it using an implied constraint, so we don't + // require `U impls Hash` beforehand (say by being declared `U:! Hash`). + fn Contains[U:! type](needle: U, haystack: HashSet(U)) -> bool; + // In this case, `Contains` is equivalent to this declaration that + // doesn't use implied constraints: + fn Contains[U:! type where .Self impls Hash] + (needle: U, haystack: HashSet(U)) -> bool; + + // In this example, the implied constraint is on something more + // complicated than a single symbolic parameter. + fn F[V:! type, W:! type](x: HashSet(Pair(V, W))); + // is equivalent to: + fn F[V:! type, W:! type where Pair(V, .Self) impls Hash] + (x: HashSet(Pair(V, W))); + ``` + + - We generally require that there always be a way to explicitly declare + constraints so implied constraints are not needed. + - Implied constraints allow declarations to be more concise, particularly + we hope that common requirements can be omitted since they are implied. + - Implied constraints are also a tool we can choose to use to say that a + requirement on a parameter can be enforced at a later point. In this + example, + + ``` + interface B {} + interface A; + class C(T:! B); + fn F(U:! A, x: C(U)); + + interface A { + require Self impls B; + } + ``` + + the symbolic type parameter `U` has a constraint that it impls `A`, and + we can add an implied constraint that it also impls `B` because it is + used as an argument to `C`. This constraint might be redundant with a + requirement from the definition of `A`, which may or may not have been + available at the point where `F` is declared. Callers have to provide + arguments that satisfy the additional requirement if it turns out that + `A` alone is not sufficient. + +### Other considerations + +Different kinds of entities have different requirements (ignoring `extern` +declarations): + +- A compile-time function must be defined before a call to it is evaluated. +- A generic function must be defined before the end of each file in which a + call to it is type-checked. +- Non-generic runtime functions may be defined separately. They may be forward + declared in an api file and defined in a corresponding impl file. +- Types must be complete before they are used in a function definition. This + includes the parameter types, the return type, the argument types of + functions that are called in the body, and the types of local variables. +- Interfaces and named constraints must be defined in the same file they are + declared. +- We want to allow some uses of classes that are declared in an api file and + defined in a corresponding impl file -- in particular, we treat the "pointer + to `C`" type `C*` to be complete even if `C` is not. + +Our main tool for making ordering easier is forward declarations. Declarations +necessarily come after all the declarations of entities named in its parameter +and return types. So declarations need to be ordered in some +[topological sorted order](https://en.wikipedia.org/wiki/Topological_sorting) +that respects those dependencies. This is unavoidable, though, as long as we are +not allowing forward references, following +[the information accumulation principle](https://docs.carbon-lang.dev/docs/project/principles/information_accumulation.html). + +Definitions will have all the dependencies of a declaration, plus perhaps some +more. As a result we will want to put them later in the file. This is possible +if a forward declaration is good enough for other declarations to use that name +in all the ways that they need to. + +## Proposal + +The goal is to allow forward declarations in more situations, and allow them to +satisfy requirements in more situations. In particular, we make these changes: + +- We accept the proposed resolution of + [leads question #4566](https://github.com/carbon-language/carbon-lang/issues/4566): + - An `impl` of an interface `I` does not `impl` any interface `I` extends. + - A single `impl` can be for only a single interface. +- We accept the proposed resolution of + [leads question #4672](https://github.com/carbon-language/carbon-lang/issues/4672): + - Assignment of associated constants is moved into the `impl` definition + in the normal case, though may still be present in a `where` clause at + the end of the declaration. + - Associated constants are assigned using `where X = value;` declarations, + with semantics matching rewrite constraints in `where` clauses. + - No part of the `impl` declaration will be excluded from syntactic match. + - Every `impl` must be defined in the same file (not just the same + library) as its declaration. + - However, the definitions of its member functions may be separate in + the impl file, out of line, as provided in + [proposal #3763](/proposals/p3763.md#out-of-line-definitions-of-associated-functions). +- An `impl` may be forward declared without the interface being defined. + - This is enabled by not needing access to the definition to see other + interfaces required or extended or to see the associated constants that + need to be given values. + - Instead we require the interface to be _identified_. This means that if + the facet type to the right of the `as` is given by a named constraint, + that named constraint needs to be complete so we can see which interface + it corresponds to. (It is an error if it doesn't correspond to a single + interface, by the resolution of + [#4566](https://github.com/carbon-language/carbon-lang/issues/4566).) + - However, an `impl` declaration of a facet type with `.A =`... rewrite + constraints (for example in a `where` clause), does still require the + interface to be complete. +- We answer questions from + [leads issue #4579](https://github.com/carbon-language/carbon-lang/issues/4579): + - Since `impl` forward declarations do not require the interface to be + defined, any requirements that other interfaces be defined from the + interface definition are ignored. + - An `impl` definition of an interface `I` requires first establishing + that the type implements any interfaces required by `I`, but that can be + satisfied by an `impl` declaration. +- We shift from a "use the information from the type definition if it happens + to be complete" model to a "only use the information from the definition in + contexts where it is required to be defined or complete" model + - However, an interface `I` containing a `require Self impls J` or + `extend J` declaration adds a fact that "types `T` satisfying + `T impls I` also satisfy `T impls J`" to the environment. + +These changes are intended to allow developers to write their api files in an +order that roughly follows: + +- namespace declarations in any order; +- other declarations in any topological dependency order; +- type (interface, constraint, class) definitions in any order; +- impl definitions in any order; +- function definitions, including both those that are needed in the api file + plus those the developer wishes to include, in any order. + +This would be sufficient except we have a few additional constraints, which +remain: + +- Performing member access into a type requires the type to be defined, not + just declared. +- Members of an effectively final impl may only be accessed after their value + has been established. +- A function that is called at compile time must be defined before that call + is executed by the compiler. + +### Prior to this proposal + +| | prereq | provides | complete by [EOF](https://en.wikipedia.org/wiki/End-of-file) | +| :---------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------- | :----------------------------------------------------------- | +| `impl C as Y;` | **`Y` complete** | `C impls Y` | | +| `impl C as Y where .A =` ... | `Y` complete | `C impls Y` | | +| `impl C as Y {` ... `}` | `Y` complete | `C impls Y` | `C` | +| `interface I;` | | `I` declared | `I` | +| `interface Y {`
` require Self impls Z;`
`}` | **`Z` declared** | **`Y` complete** | `Z` | +| `interface Y {`
` require Self impls Z;`
`}`
`impl C as Y {` ... `}` | **open question [\#4579](https://github.com/carbon-language/carbon-lang/issues/4579)** | **open question [\#4579](https://github.com/carbon-language/carbon-lang/issues/4579)** | | +| `fn F[T:! I](x: T);` | `I` declared | | `F`, `I` | +| `fn F[T:! I](x: T) {` ... `}` | `I` complete | `F` complete | | +| `interface I;`
`class C;`
`class D(T:! I);`
`fn F(x: D(C));` | `C impls I` | | `I`, `C`?, `D` | +| `interface I;`
`class D(T:! I);`
`fn F[U:! type](x: D(U));` | | `U impls I`
(implied constraint) | `I`, `D`, `F` | + +### Proposed rules + +| | prereq | provides | complete by [EOF](https://en.wikipedia.org/wiki/End-of-file) | +| :---------------------------------------------------------------------------------- | :----------------- | :------------------------------------------------------------------------------------- | :----------------------------------------------------------- | +| `impl C as Y;` | **`Y` identified** | `C impls Y` | **`Y`, `impl C as Y`** | +| `impl C as Y where .A =` ... | `Y` complete | `C impls Y` | **`impl C as Y` ...** | +| `impl C as Y {` ... `}` | `Y` complete | `C impls Y` | `C` | +| `interface I;` | | `I` declared | `I` | +| `interface Y {`
` require Self impls Z;`
`}` | **`Z` identified** | **for any symbolic type `T`,
`T impls Y` implies `T impls Z`;
`Y` complete** | `Z` | +| `interface Y {`
` require Self impls Z;`
`}`
`impl C as Y {` ... `}` | **`C impls Z`** | **`C impls Y`** | | +| `fn F[T:! I](x: T);` | `I` declared | | `F`, `I` | +| `fn F[T:! I](x: T) {` ... `}` | `I` complete | `F` complete | | +| `interface I;`
`class C;`
`class D(T:! I);`
`fn F(x: D(C));` | `C impls I` | | `I`, `C`?, `D` | +| `interface I;`
`class D(T:! I);`
`fn F[U:! type](x: D(U));` | | `U impls I`
(implied constraint) | `I`, `D`, `F` | + +> Whether classes need to be complete by the end of the file is not the subject +> of this proposal. + +## Details + +### An `impl` implements a single interface + +We resolve +[leads question #4566](https://github.com/carbon-language/carbon-lang/issues/4566) +with two rules: + +- Implementing an interface does not implement any of the interfaces it + extends. +- An impl declaration should implement a single interface. + +We allow facet type expressions to the right of `impl`...`as`, as long as that +facet type corresponds to a single interface (ignoring interfaces it extends, +which incidentally seems to +[match Rust supertraits](https://stackoverflow.com/questions/75847426/why-do-we-need-a-separate-impl-for-a-supertrait)). +This supports the use case of `Core.Add` actually being a named constraint +defined in terms of `Core.AddWith`. This requires that the facet type is +identified, at a minimum, when used in an `impl` declaration, so it can be +resolved into the single interface actually being implemented. + +The "subsumption" use case of "when an interface X is implemented, Y will be +implemented without the user having to write a separate impl definition" will +instead be handled using +[blanket implementations](/docs/design/generics/details.md#blanket-impl-declarations). +There are a couple of variations: + +- One interface is a strict superset of the other. In this case it would be a + lot less confusing/surprising if the interfaces use the same implementations + of functions that overlap. This is accomplished using a + [`final`](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/generics/details.md#final-impl-declarations) + blanket implementation. For example, we will define + `final impl forall [T:! type, U:! ImplicitAs(T)] U as As(T)` and have + `As(T).Convert` forward to `ImplicitAs(T).Convert`. This way types will + either implement `ImplicitAs(T)` or `As(T)`, and will get an error if they + try to implement both. + +- One interface can be implemented in terms of the other. For example, if + `Ordered` implies an implementation of `IsEqual`, types might still want to + provide an explicit definition of `IsEqual` when they can do so more + efficiently. This would use a non-`final` blanket implementation. + +Note that the orphan rule prevents this blanket impl from being written unless +the two interfaces in a subsumption relationship are in the same library. +Support for these use cases is +[future work](#addressing-the-subsumption-use-case-previously-addressed-by-extend-in-interfaces). +For now, `extend I;` in an interface definition continues to mean that `I` is +required and the names of `I` are included as aliases, matching the meaning in +named constraints, see "interface extension" in +[proposal #553](https://github.com/carbon-language/carbon-lang/pull/553). + +### Associated constants may be assigned in the body of an `impl` definition + +We resolve +[leads question #4672](https://github.com/carbon-language/carbon-lang/issues/4672) +with the following updated rules: + +- The `impl` definition must be in the same file as its owning declaration. +- The first declaration establishes that the type implements the interface. +- That declaration may optionally specify a subset of the associated constants + in a `where` clause. The `where` clause syntax is the same as before this + proposal, but the requirement that all associated constants be given their + final values (or accept their defaults where available) is removed. + - This adds some flexibility to ordering, allowing a developer to say that + a type implements an interface before being able to specify the values + of its associated constants, while also allowing a subset to be + specified if some later declaration needs to access their values. +- Redeclarations of an `impl` no longer have any special treatment of `where` + clauses. + - Each redeclaration is required to match syntactically, so all will have + the same `where` clause, if any. + - The part between `impl` and the end of the declaration (marked by a `{` + or `;`) is the name of the `impl`. This will be used in places where we + want to name the `impl`, such as in an impl priority ("match first") + block or out-of-line definitions of `impl` members. +- Associated constant assignments should be encouraged to be placed in the + `impl` definition rather than in a `where` clause in the declaration, unless + needed in the declaration to resolve dependencies. + - This makes the name of the `impl` shorter and easier to state. + - The syntax for specifying the value of an associated constant in an + `impl` definition is `where X = value;`, just like a rewrite clause in a + `where` expression, except that `where` in this case is an introducer + rather than a binary operator, and the names of the associated constants + are in scope, so there is no need to prefix them with a period (`.`). +- The non-function associated constants are fixed at the closing curly `}` of + the `impl` definition. At that point, each associated constant that has not + been assigned a value is given its default value. If it does not have a + default, an error diagnostic is issued. +- The associated function declarations are fixed by the end of the `impl` + definition. + - Only associated functions with defaults may be omitted from the `impl` + definition. + - Not mentioning an associated function with a default in the `impl` + definition means the default version from the interface will be used. + - Associated functions declared in the `impl` definition must have a + matching definition. + - If the declared signature of an associated function in the `impl` + definition does not match the signature given in the `interface` + definition, but are compatible, a stub function that does the conversion + is generated to bridge between the two versions. +- Out-of-line function bodies are allowed in the impl file, even when the + `impl` definition is in the api file. + - This parallels how function definitions are not part of the definition + of classes. +- Missing function body definitions are diagnosed at link time. + +### An `impl` may be forward declared without the interface being complete + +We have removed the reasons to require that the interfaces being implemented are +defined for an `impl` forward declaration: + +- We no longer need to see which interfaces are required or extended from the + definition of the interface, since the `impl` no longer implements those. +- We no longer need to see which associated constants are members of the + interface to see if a declaration specifies values for all of them. +- If a `where` clause is used in the declaration, the interface must be + defined in order to name the associated constants, but `where` clauses are + discouraged. + +However, if the facet type being implemented is a named constraint, we do need +that to be complete so we can resolve the interface it resolves to. (It is an +error if it doesn't correspond to a single interface, by the resolution of +[#4566](https://github.com/carbon-language/carbon-lang/issues/4566).) + +This means that generally we can establish that a type implements an interface +right after they are both declared. + +### Interface requirements + +[Leads issue #4579](https://github.com/carbon-language/carbon-lang/issues/4579) +concerns itself with interfaces that require other interfaces to be implemented, +as in: + +``` +interface Z; + +interface Y { + require Self impls Z; +} +``` + +There are two sides to this: what do you have to establish about `Z` before an +`impl` declaration or definition that a type implements `Y`, and what does a +declaration or definition that a type implements `Y` establish about `Z`? + +We adopt the following rules: + +- Since `impl` forward declarations do not require the interface to be + defined, any requirements that other interfaces be defined from the + interface definition are ignored. + + ``` + class C1; + // ✅ Allowed + impl C1 as Y; + + // `class C1` and `impl C1 as Y` must be defined in the same file. + ``` + +- An `impl` definition of an interface `I` requires first establishing that + the type implements any interfaces required by `I`, but that can be + satisfied by an `impl` declaration. + + ``` + class C2; + // ❌ Invalid, must first establish `C2 impls Z`. Still invalid if + // `impl C2 as Z` appears later in the file. + impl C2 as Y {} + + class C3; + // ✅ Allowed + impl C3 as Z; + impl C3 as Y {} + + class C4; + // ✅ Allowed + impl C4 as Z {} + impl C4 as Y {} + + // The classes and `impl C3 as Z` must be defined in the same file. + ``` + +This is is aligned with the shift from a "use the information from the type +definition if it happens to be complete" model to a "only use the information +from the definition in contexts where it is required to be defined or complete" +model that this proposal this is switching to. To recapture some of that context +sensitivity, we say that the definition of an interface with requirements, like +`Y` above, introduces the extra information that "symbolic types `T` that +satisfy `T impls Y` also satisfy `T impls J`." + +For example: + +``` +interface Z; + +interface Y { + require Self impls Z; +} + +class C(T:! Z); + +class D(U:! Y) { + // ✅ U impls Y so it also impls Z. + // + // Wouldn't use implied constraints here since `U` is + // from a containing scope. + fn F(x: C(U)); +} +``` + +This rule only applies to symbolic types, since we want to only say that +concrete types implement an interface if we have a declared `impl` to witness +that fact. Symbolic types depend on the value of some generic parameters and we +accept that some accesses of interface members will result in symbolic values +that will only have known values once concrete argument values are supplied for +the generic parameters. For concrete types, the access is performed immediately, +and it is an error if we don't have an `impl` declaration we can point to with +the witness. For concrete types, there is no later point where an argument is +supplied to delay these checks. For example: + +``` +interface Z; + +interface Y { + require Self impls Z; +} + +class C(T:! Z); + +class D {} + +// ✅ Allowed, since `impl` declarations are allowed for declared +// entities. Interface requirements are not considered. +impl D as Y; + +// ❌ Error: D is not known to implement Z. The fact that Y +// requires Z is not used since D is a concrete type. +fn F(x: C(D)); + +// Too late to affect the previous declaration, by the information +// accumulation principle. +impl D as Z; +``` + +Similarly, it is also an error to access a member of an `impl` of a concrete +type that doesn't have a known value, even if it is given a value later in the +file. For example: + +``` +interface I { + let A:! type; + let B:! type; +} + +fn F(T:! I) -> T.A; + +class C {} +class D {} + +impl C as I; + +fn G1() { + // ❌ Error: C impls I, but C.(I.A) is unknown. + var x: auto = F(C); + // ❌ Error: C.(I.A) is unknown. + let y: C.(I.A) = 0; + // ❌ Error: C.(I.B) is unknown. + let b: C.(I.B) = false; +} + +impl D as I where .A = i32; + +fn H() { + // ✅ Allowed: D.(I.A) = i32; + var x: auto = F(D); + var y: D.(I.A) = 0; + // ❌ Error: D.(I.B) is unknown. + let b: D.(I.B) = false; +} + +impl C as I { + where A = i32; + where B = bool; +} + +fn G2() { + // ✅ Allowed: C.(I.A) = i32; + var x: auto = F(C); + let y: C.(I.A) = 0; + // ✅ Allowed: C.(I.B) = bool; + let b: C.(I.B) = false; +} +``` + +### Examples + +This example shows using incomplete entities following the rules of this +proposal: + +``` +interface X; + +// ✅ Allowed to use incomplete interfaces in function declarations. +fn F(U:! X); + +class C; + +// ✅ Allowed to use incomplete types and interfaces in impl declarations. +impl C as X; + +interface Y; + +interface X { + // ✅ Allowed to use an incomplete interface. + require Self impls Y; +} + +// Classes must be defined before being used in a function definition. +class C { ... } + +fn G() { + // ✅ Allowed since C is complete and we have a declaration `impl C as X;` + F(C); +} + +// The above declarations require that `interface Y`, `fn F` (since it is +// generic), and `impl C as X` are defined in the same file. +interface Y { ... } +fn F(U:! X) { ... } +// Required for `impl C as X` definition. +impl C as Y; +impl C as X { ... } +impl C as Y { } +``` + +This example demonstrates using associated constants and interface requirements: + +``` +interface X2 { + let A:! type; + let B:! type; +} +interface Y2 { + require Self impls X2; + // ✅ Allowed since `X2` is required and we can access + // its `A` member since `X2` is complete. + fn F() -> X2.A; +} + +class C2 {} + +impl C2 as X2 where .A = i32; + +// ✅ Allowed since `C2` and `Y2` are complete, and +// `C2 impls X2` so `C2` satisfies the requirement in `Y2`. +impl C2 as Y2 { + // ✅ Allowed since the value of `C2.(X2.A)` is known to + // be `i32`, even though `impl C2 as X2` is not complete. + fn F() -> i32; +} + +// There needs to be a definition of `C2 as X2 where .A = i32` +// in the same file. The `where` clause needs to be repeated +// verbatim, since redeclaration requires a syntactic match. +impl C2 as X2 where .A = i32 { + // Remaining members of `X2` that do not have default + // values need to be assigned. + where B = i32; +} +``` + +## Future work + +### Addressing the subsumption use case previously addressed by `extend` in interfaces + +We do expect to have collections of interfaces that have a stronger "extending" +relationship than is provided by the current `extend` declaration in interfaces. +For example, a type implementing `ImplicitAs` should not have to have a separate +declaration that it also implements `As`. Addressing this use case is out of +scope of this proposal, though, and will be addressed in a future proposal. This +future proposal may include: + +- A feature to copy the members of an interface into another to make the + subsumption use case easier to write and involve less duplication. + +- A feature to define an implementation of an interface in terms of another by + forwarding, for similar reasons. + +- A `final` version of the `match_first`/`impl_priority` feature to resolve + conflicts when multiple interfaces want to subsume a common interface. We + likely want a feature like this for function overloading as well. + +- Some way of handling an `impl` that could overlap with a `final impl`, but + doesn't in practice. + +- Possible support for implementing multiple interfaces with a single impl + definition, as the result of using an `&` operator or named constraint to + the right of `as`, as in + [the considered alternative](#allow-implementing-multiple-interfaces-with-a-single-impl-declaration). + +For now, we leave `extend` as meaning "requires plus include aliases of the +names," matching the behavior in named constraints. But this will be +reconsidered once we have support for this other use case. + +### Opt-in to using the interface's default definition of an associated function + +We've considered that we may want to allow an `impl` to opt into using the +default definition of a function from the interface by writing `= default;` +instead of an inline body in curly braces `{`...`}`. We will see if that is a +desirable construct to add with experience. This idea was suggested in +[issue #4672](https://github.com/carbon-language/carbon-lang/issues/4672#issuecomment-2539845620). + +## Rationale + +The specific solution was chosen to align with +[the information accumulation principle](https://docs.carbon-lang.dev/docs/project/principles/information_accumulation.html). +In particular, allowing `impl` declarations for incomplete interfaces gives +additional flexibility to developers to satisfy those constraints. + +By reducing the different behavior based on whether a previous declaration was a +definition, this proposal reduces complexity in the toolchain and tools that +operate on Carbon source code. This benefits the +[Language tools and ecosystem](/docs/project/goals.md#language-tools-and-ecosystem) +and +[Fast and scalable development](/docs/project/goals.md#fast-and-scalable-development) +[Carbon goals](/docs/project/goals.md). + +This is intended to also help humans have a simpler mental model of the +compiler, to help the +[Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write) +goal. + +## Alternatives considered + +The trade offs and alternatives were discussed in +[this document](https://docs.google.com/document/d/1NNhBU5tywGkeneLmTQ4DwHyCp5vYlwTiR_wmnjn9wnM/edit) +and in open discussion meetings on these dates: + +- [2025-02-07](https://docs.google.com/document/d/1Iut5f2TQBrtBNIduF4vJYOKfw7MbS8xH_J01_Q4e6Rk/edit?resourcekey=0-mc_vh5UzrzXfU4kO-3tOjA&tab=t.0#heading=h.6ziehwl15x6a) +- [2025-03-04](https://docs.google.com/document/d/1Iut5f2TQBrtBNIduF4vJYOKfw7MbS8xH_J01_Q4e6Rk/edit?resourcekey=0-mc_vh5UzrzXfU4kO-3tOjA&tab=t.0#heading=h.22fvs78tr2ej) +- [2025-03-05](https://docs.google.com/document/d/1Iut5f2TQBrtBNIduF4vJYOKfw7MbS8xH_J01_Q4e6Rk/edit?resourcekey=0-mc_vh5UzrzXfU4kO-3tOjA&tab=t.0#heading=h.x0brmxoi93n4) +- [2025-03-06](https://docs.google.com/document/d/1Iut5f2TQBrtBNIduF4vJYOKfw7MbS8xH_J01_Q4e6Rk/edit?resourcekey=0-mc_vh5UzrzXfU4kO-3tOjA&tab=t.0#heading=h.lsqxlvgwdvoh) +- [2025-03-13](https://docs.google.com/document/d/1Iut5f2TQBrtBNIduF4vJYOKfw7MbS8xH_J01_Q4e6Rk/edit?resourcekey=0-mc_vh5UzrzXfU4kO-3tOjA&tab=t.0#heading=h.lsufbqunqul0) +- [2025-03-18](https://docs.google.com/document/d/1Iut5f2TQBrtBNIduF4vJYOKfw7MbS8xH_J01_Q4e6Rk/edit?resourcekey=0-mc_vh5UzrzXfU4kO-3tOjA&tab=t.0#heading=h.v0stu27nansb) +- [2025-03-20](https://docs.google.com/document/d/1Iut5f2TQBrtBNIduF4vJYOKfw7MbS8xH_J01_Q4e6Rk/edit?resourcekey=0-mc_vh5UzrzXfU4kO-3tOjA&tab=t.0#heading=h.jagz2i1dynjx) + +### Allow implementing multiple interfaces with a single `impl` declaration + +This was considered in +[leads question #4566](https://github.com/carbon-language/carbon-lang/issues/4566). +There are definite use cases for this feature, particularly arising from +evolution. For example, you might want to split an interface into two new +interfaces, and have a named constraint with the original name extend both so +that existing code continues to work the same. With this proposal, those changes +will be harder and have more steps. + +Two specific approaches to implementing multiple interfaces were eliminated from +consideration in that issue: + +- The "all or nothing" approach where the `impl` definition is used for all of + the interfaces, or none of them are. This would create too much uncertainty + about whether an `impl` is applicable, particularly since constraints in + generic code are not sensitive to whether something is specialized. +- The "Constrained impls" approach where an `impl` of multiple interfaces is + treated as a collection of `impl`s of the individual interfaces with the + additional constraint that no specialization changes the values of any + non-function associated constants of any of the interfaces. Those + constraints though are ultimately circular and not well defined. + +The remaining "independent impls" approach seemed possible. In this approach an +impl of multiple interfaces is treated as a collection of impls of the +individual interfaces. In particular, the definition of a member of one +interface can assume that the other interfaces are implemented, but not that the +associated types (or other non-function associated constants) have expected +values. This would introduce some complexities and a number of questions would +need to be answered around how a single `impl` definition would be split into +definitions of the individual interfaces, how dependencies between those pieces +would be resolved, and how these restrictions would be exposed to the user in +diagnostics. + +### An `impl` of an interface also implements the interfaces it extends + +This was the design before this proposal, but in +[leads question #4566](https://github.com/carbon-language/carbon-lang/issues/4566) +we found a number of problems with that approach: + +- A parameterized interface extending a non-parameterized interface, or an + interface with fewer parameters, leads to multiple implementations of the + extended interface. + +- There are multiple possible semantics you might want, and having a single + `impl` does not provide the affordances for choosing between those options, + where one `impl` per interface would. For example, in + `impl forall [T:! type] C(T) as I & J where .(I.x) = i32 and .(J.y) = .(I.x)`, + if there is a specialization of `C(T)` for `I`, will `J.y` have the value + `i32` or the `I.x` from the specialization? In practice, the semantics of + rewrites mean that `.(I.x)` is replaced with `i32` at an early stage in the + compiler (to support things like `.(J.y) = .(I.x).D`), and so only the first + option is consistent. This is a particular concern for the "Independent + impls" option above. If this `impl` is split into two, then the different + possible meanings have different spellings: + + - `impl forall [T:! type] C(T) as J where .(J.y) = i32` means `J.y` will + be `i32` independent of any specialization of `C(T)` for `I` + + - `impl forall [T:! type where C(T) impls I] C(T) as J where .(J.y) = .(I.x)` + means `J.y` matches `I.x` even if `C(T)` is specialized + + - `impl forall [T:! type where C(T) impls (I where .x = i32)] C(T) as J where .(J.y) = .(I.x)` + means this impl won't be used unless `I.x` is `i32`. Note this last form + approximates the "Constrained impls" approach above, but with an + explicit ordering to determine the semantics, and the existing language + rules preventing the code from declaring cycles that would make it + ambiguous. + +- If an interface `J` extends `I` but they are defined in distinct libraries, + there is no guarantee that an implementation of `J` belongs in the same + library as an implementation of `I` for the same type due to the orphan + rule. + +- The current documented rule for which interfaces an impl implements is those + whose members are defined in the interface definition. This rule is + ambiguous for empty interfaces or interfaces where all the associated + functions have defaults. It also requires + [a lot of context](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/project/principles/low_context_sensitivity.md) + to answer that question (this was intentional, to allow options for + refactoring an interface without having to update implementations of it). In + addition to being the source of readability concerns, this muddies the + meaning of `impl` declarations, and make the compiler implementation much + trickier (the compiler can't say what an impl declaration provides at the + point where it is written, making it hard to give that declaration a clear + type in the SemIR). + +- Ideas we considered to determine the interfaces implemented from only the + impl declaration ran into problems. Without being able to control which + interfaces an impl is defining, then it isn't clear how to handle + implementing two interfaces that have common interface they both extend + unless you implement them both in a single impl definition (which may not + even be possible due to the orphan rule). Another idea we had in this space + was a way to say "this interface minus one of the interfaces it extends" + (maybe `J \ I`?). + +We considered some restrictions on `extend` to address some of these concerns: + +- Perhaps implementing an interface only implemented the interfaces it extends + that have to be defined in this library by the orphan rule. + +- Perhaps an interface may only extend an interface defined in the same + library. + +- Perhaps an interface may only be extended by a single interface. This + precluded motivating use cases for `extend`, though, like the subtyping + relationships between different kinds of graphs used in + [the Boost Graph library](https://docs.google.com/document/d/15Brjv8NO_96jseSesqer5HbghqSTJICJ_fTaZOH0Mg4/edit?resourcekey=0-CYSbd6-xF8vYHv9m1rolEQ&tab=t.0)). + +Changing the meaning of `extend` is left for a future proposal, when we address +[the subsumption use case previously addressed by `extend` in interfaces](#addressing-the-subsumption-use-case-previously-addressed-by-extend-in-interfaces). + +### Use the contents of definitions if available + +In the course of implementing the existing design, uses of "TryToCompleteType" +function were found to be prone to leading to coherence issues. If being +incomplete does not lead to an error, we need to establish that the results of +the definition appearing before and after that test are the same. When there +were multiple types involved, this led to an explosion of combinations to test. +The new model has less conditional logic, and as a result less complexity. + +### Delayed checking of incomplete types + +We could delay checking uses of incomplete types until some later point, either +when the type is complete, a use that requires that type to be complete, or the +end of the file where we know the most about it. This is an option, and we might +adopt it if we found that it was too hard in practice to satisfy the constraints +adopted by this proposal. However it would significantly increase the complexity +of the toolchain implementation, which would lead to a corresponding increase in +the difficulty of understanding how the code will be interpreted. + +### Allow `impl` declarations with rewrites of defined but not complete interfaces + +We at first did not have a rule saying that `I` needed to be complete in +`impl C as I where .X = ...`. The rationale was that accessing members of `I` +only needed `I` to be defined, not complete. However, an `impl` declaration +inside the definition of the interface being implemented could ultimately never +be defined, even if we allowed the declaration. This is because the `impl` +definition would have to be in a different scope than its declaration, as can be +seen in this example that uses a lambda function to get a scope that can have an +`impl` declaration inside an `interface` definition: + +``` +interface I { + let U:! type; + default let T:! type = (fn() -> type { + class C { } + + // ✅ Allowed since `I` is declared, with the exception that + // this requires a definition before the end of the file. + impl C as I; + + // ✅ Would be allowed since `I` is defined, including its + // member `U`. Again this requires a definition in this file. + impl C as I where .U = C; + + // ❌ Neither of the above impls may be defined: + // - Can't be defined here since `I` is not complete. + // - Can't define after `I` is complete, since redeclarations + // must match syntactically and have no hope of naming this + // `C` that way. + // - In general, the impl definition would have to re-enter the + // same scope. + impl C as I where .U = C {} + + return C; + })(); +} +``` + +This simplifies the toolchain implementation of this feature, since it means we +can create an `impl` witness for declarations that use rewrite constraints with +the full knowledge of the interface's definition. + +### Can omit function declarations from `impl` body + +We considered saying that an `impl` definition does not need to include +declarations that are unchanged from the interface definition. However, this +raised a number of questions and problems: + +- The `interface` definition is in a different scope from the `impl`, and + potentially a different file. This, combined with the evolution problems of + depending on the specific token sequence used in the `interface` definition, + suggest that we would need a different matching rule that was semantic + instead of syntactic. +- Having a different declaration matching rule was anticipated as creating a + bunch of difficulties once function overloading is added to Carbon, + particularly overloaded functions in interfaces. +- Without a declaration in the `impl` body, there were a number of problems + resolving how to handle a function in the `impl` that was compatible with + but a different signature from the interface, and when that different + signature would be used. +- These issues seemed to magnify if the interface had a default definition for + the function. + +### Different introducer for assigning associated constants in an `impl` definition + +Other options we considered were `provides`, `alias`, and whatever we eventually +choose for [#5028](https://github.com/carbon-language/carbon-lang/issues/5028). +We ultimately liked matching how you would assign associated constants in the +declaration using the `where` operator and a rewrite constraint, particularly +since we wanted to use the same semantics.