diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index c88473983771d..3333dc812f63e 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -17,6 +17,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Implementing multiple interfaces](#implementing-multiple-interfaces) - [External impl](#external-impl) - [Qualified member names](#qualified-member-names) + - [Access](#access) - [Generics](#generics) - [Implementation model](#implementation-model) - [Interfaces recap](#interfaces-recap) @@ -35,6 +36,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Extending adapter](#extending-adapter) - [Use case: Using independent libraries together](#use-case-using-independent-libraries-together) - [Use case: Defining an impl for use by other types](#use-case-defining-an-impl-for-use-by-other-types) + - [Use case: Private impl](#use-case-private-impl) - [Adapter with stricter invariants](#adapter-with-stricter-invariants) - [Associated constants](#associated-constants) - [Associated class functions](#associated-class-functions) @@ -591,6 +593,24 @@ p.(Plot.Drawable.Draw)(); C++, adding `ClassName::` in front of a member name to disambiguate, such as [names defined in both a parent and child class](https://stackoverflow.com/questions/357307/how-to-call-a-parent-class-function-from-derived-class-function). +### Access + +An `impl` must be visible to all code that can see both the type and the +interface being implemented: + +- If either the type or interface is private to a single file, then since the + only way to define the `impl` is to use that private name, the `impl` must + be defined private to that file as well. +- Otherwise, if the type or interface is private but declared in an API file, + then the `impl` must be declared in the same file so the existence of that + `impl` is visible to all files in that library. +- Otherwise, the `impl` must be defined in the public API file of the library, + so it is visible in all places that might use it. + +No access control modifiers are allowed on `impl` declarations, an `impl` is +always visible to the intersection of the visibility of all names used in the +declaration of the `impl`. + ## Generics Here is a function that can accept values of any type that has implemented the @@ -1850,6 +1870,43 @@ class IntWrapper { } ``` +### Use case: Private impl + +Adapter types can be used when a library publicly exposes a type, but only wants +to say that type implements an interface as a private detail internal to the +implementation of the type. In that case, instead of implementing the interface +for the public type, the library can create a private adapter for that type and +implement the interface on that instead. Any member of the class can cast its +`me` parameter to the adapter type when it wants to make use of the private +impl. + +``` +// Public, in API file +class Complex64 { + // ... + fn CloserToOrigin[me: Self](them: Self) -> bool; +} + +// Private + +adapter ByReal extends Complex64 { + // Complex numbers are not generally comparable, + // but this comparison function is useful for some + // method implementations. + impl as Comparable { + fn Less[me: Self](that: Self) -> bool { + return me.Real() < that.Real(); + } + } +} + +fn Complex64.CloserToOrigin[me: Self](them: Self) -> bool { + var me_mag: ByReal = me * me.Conj() as ByReal; + var them_mag: ByReal = them * them.Conj() as ByReal; + return me_mag.Less(them_mag); +} +``` + ### Adapter with stricter invariants **Future work:** Rust also uses the newtype idiom to create types with @@ -3591,3 +3648,4 @@ parameter, as opposed to an associated type, as in `N:! u32 where ___ >= 2`. - [#553: Generics details part 1](https://github.com/carbon-language/carbon-lang/pull/553) - [#731: Generics details 2: adapters, associated types, parameterized interfaces](https://github.com/carbon-language/carbon-lang/pull/731) - [#818: Constraints for generics (generics details 3)](https://github.com/carbon-language/carbon-lang/pull/818) +- [#931: Generic impls access (details 4)](https://github.com/carbon-language/carbon-lang/pull/931) diff --git a/proposals/p0931.md b/proposals/p0931.md new file mode 100644 index 0000000000000..7637199e40044 --- /dev/null +++ b/proposals/p0931.md @@ -0,0 +1,143 @@ +# Generic impls access (details 4) + + + +[Pull request](https://github.com/carbon-language/carbon-lang/pull/931) + + + +## Table of contents + +- [Problem](#problem) +- [Background](#background) +- [Proposal](#proposal) +- [Rationale based on Carbon's goals](#rationale-based-on-carbons-goals) +- [Alternatives considered](#alternatives-considered) + - [Private impls for public types](#private-impls-for-public-types) + - [Private interfaces in public API files](#private-interfaces-in-public-api-files) + + + +## Problem + +Should a type be able to declare an `impl` to be private, like it can for its +data and methods? There are some use cases for this feature, but those use cases +generally could also be addressed by implementing the interface on a private +adapter type instead. + +## Background + +Private impls have been considered but not implemented for Swift in the +[scoped conformances pitch](https://forums.swift.org/t/scoped-conformances/37159). + +## Proposal + +This is a proposal to add to +[this design document on generics details](/docs/design/generics/details.md). +The additions are: + +- to say impls do not allow access control modifiers and are always as public + as the names used in the signature of the impl; and +- to document how to use private adapter types instead. + +## Rationale based on Carbon's goals + +We decided to make generics coherent as part of accepting proposal +[#24: generics goals](https://github.com/carbon-language/carbon-lang/pull/24). +This approach is consistent with broader Carbon goals that Carbon have +[code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write). +In particular, we favor making code +[less context sensitive](/docs/project/principles/low_context_sensitivity.md). + +## Alternatives considered + +### Private impls for public types + +We considered supporting private impls for public types. This was motivated by +using the conversion of a `struct` type to a `class` type to construct class +values. In the case that the class had private data members, we wanted to +restrict that conversion so it was only available in the bodies of class members +or friends of the class. We considered achieving that goal by saying that the +implementation of the conversion would be private in that case. Allowing impls +to generally be private, though, led to a number of coherence concerns that the +same code would behave differently in different files. Our solution was to +address these conversions using a different approach that only addressed the +conversion use case: + +- Users could not implement `struct` to `class` conversions themselves. +- The compiler would generate `struct` to `class` conversions for constructing + class values itself. +- Those conversion impls will always be as visible as the class type, and will + still be selected using the same rules as other impls. +- When one of these compiler-generated conversion impls is selected, the + compiler would perform an access control check to see if it was allowed. + +We believe that other use cases for restricting access to an impl are better +accomplished by using a private adapter type than supporting private impls. + +### Private interfaces in public API files + +A private interface may only be implemented by a single library, which gives the +library full control. We considered and rejected the idea that developers could +put that interface declaration in an API file to allow it to be referenced in +named constraints available to users. All impls for that interface would also +have to be declared in the API file, unless they were for a private type +declared in that library. + +This would allow you to express things like: + +- A named constraint that is satisfied for any type **not** implementing + interface `Foo`: + + ``` + class TrueType {} + class FalseType {} + private interface NotFooImpl { + let IsFoo:! Type; + } + impl [T:! Foo] T as NotFooImpl { + let IsFoo:! Type = TrueType; + } + impl [T:! Type] T as NotFooImpl { + let IsFoo:! Type = FalseType; + } + constraint NotFoo { + extends NotFooImpl where .IsFoo == FalseType; + } + ``` + +- A named constraint that ensures `CommonType` is implemented symmetrically: + + ``` + // For users to implement for types + interface CommonTypeWith(U:! Type) { + let Result:! Type where ...; + } + + // internal library detail + private interface AutoCommonTypeWith(U:! Type) { + let Result:! Type where ...; + } + + // To use as the requirement for parameters + constraint CommonType(U:! AutoCommonTypeWith(Self)) { + extends AutoCommonTypeWith(U) where .Result == U.Result; + } + + match_first { + impl [T:! Type, U:! ManualCommonTypeWith(T)] + T as AutoCommonTypeWith(U) { + let Result:! auto = U.Result; + } + impl [T:! Type, U:! ManualCommonTypeWith(T)] + U as AutoCommonTypeWith(T) { + let Result:! auto = U.Result; + } + } + ``` + +This feature is something we might consider adding in the future.