Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic impls access (details 4) #931

Merged
merged 11 commits into from
Dec 7, 2021
82 changes: 67 additions & 15 deletions docs/design/generics/details.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
- [Model](#model)
- [Interfaces recap](#interfaces-recap)
Expand All @@ -34,8 +35,9 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- [Adapter compatibility](#adapter-compatibility)
- [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)
- [Application: Defining an impl for use by other types](#application-defining-an-impl-for-use-by-other-types)
- [Associated constants](#associated-constants)
- [Associated class functions](#associated-class-functions)
- [Associated types](#associated-types)
Expand Down Expand Up @@ -587,6 +589,19 @@ 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 as public as the type and interface being implemented. If
zygoloid marked this conversation as resolved.
Show resolved Hide resolved
either the type or interface is private to a library, then since the only way to
define the `impl` is to use that private name, the `impl` must be defined
private to the library as well. 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

Now let us write a function that can accept values of any type that has
Expand Down Expand Up @@ -1768,20 +1783,7 @@ var song: Song = ...;
CompareLib.Sort((song,));
```

### Adapter with stricter invariants

**Future work:** Rust also uses the newtype idiom to create types with
additional invariants or other information encoded in the type
([1](https://doc.rust-lang.org/rust-by-example/generics/new_types.html),
[2](https://doc.rust-lang.org/book/ch19-04-advanced-types.html#using-the-newtype-pattern-for-type-safety-and-abstraction),
[3](https://www.worthe-it.co.za/blog/2020-10-31-newtype-pattern-in-rust.html)).
This is used to record in the type system that some data has passed validation
checks, like `ValidDate` with the same data layout as `Date`. Or to record the
units associated with a value, such as `Seconds` versus `Milliseconds` or `Feet`
versus `Meters`. We should have some way of restricting the casts between a type
and an adapter to address this use case.

### Application: Defining an impl for use by other types
### Use case: Defining an impl for use by other types

Let's say we want to provide a possible implementation of an interface for use
by types for which that implementation would be appropriate. We can do that by
Expand Down Expand Up @@ -1812,6 +1814,55 @@ 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, create a private adapter for that type and implement the
interface on that instead. Any member of the class can cast its `me` parameter
zygoloid marked this conversation as resolved.
Show resolved Hide resolved
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
additional invariants or other information encoded in the type
([1](https://doc.rust-lang.org/rust-by-example/generics/new_types.html),
[2](https://doc.rust-lang.org/book/ch19-04-advanced-types.html#using-the-newtype-pattern-for-type-safety-and-abstraction),
[3](https://www.worthe-it.co.za/blog/2020-10-31-newtype-pattern-in-rust.html)).
This is used to record in the type system that some data has passed validation
checks, like `ValidDate` with the same data layout as `Date`. Or to record the
units associated with a value, such as `Seconds` versus `Milliseconds` or `Feet`
versus `Meters`. We should have some way of restricting the casts between a type
and an adapter to address this use case.

## Associated constants

In addition to associated methods, we allow other kinds of
Expand Down Expand Up @@ -3539,3 +3590,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)
142 changes: 142 additions & 0 deletions proposals/p0931.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# 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 impls to be private, like it can for its
josh11b marked this conversation as resolved.
Show resolved Hide resolved
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

The idea is that a private interface may only be implemented by a single
zygoloid marked this conversation as resolved.
Show resolved Hide resolved
library, which gives the library full control. However, putting that interface
declaration in an API file would 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 T:! Type;
}
impl [T:! Foo] T as NotFooImpl {
let T:! Type = FalseType;
}
impl [T:! Type] T as NotFooImpl {
let T:! Type = TrueType;
}
constraint NotFoo {
extends NotFooImpl where .T == TrueType;
}
```

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