mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 18:50:09 +01:00
Generic impls access (details 4) (#931)
Implementations of interfaces are as public as the names used in their signature. No access control modifiers are allowed on `impl` declarations. Co-authored-by: Richard Smith <richard@metafoo.co.uk>
This commit is contained in:
committed by
GitHub
co-authored by
Richard Smith
parent
3f759ed58c
commit
be8d0a993b
@@ -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)
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
# Generic impls access (details 4)
|
||||
|
||||
<!--
|
||||
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/931)
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
## 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)
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
## 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.
|
||||
Reference in New Issue
Block a user