Skip to content

Commit 2f09a99

Browse files
committed
Initial draft of simple non-Copyable protocols proposal
1 parent bf3eec7 commit 2f09a99

File tree

1 file changed

+169
-0
lines changed

1 file changed

+169
-0
lines changed
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# Support ~Copyable, ~Escapable in simple standard library protocols
2+
3+
* Proposal: [SE-NNNN](NNNN-make-stdlib-protocols-escapable.md)
4+
* Authors: [Ben Cohen](https://github.com/airspeedswift)
5+
* Review Manager: TBD
6+
* Status: **Awaiting Review**
7+
* Implementation: [swiftlang/swift#85079](https://github.com/swiftlang/swift/pull/85079)
8+
9+
## Introduction
10+
11+
The following protocols will be marked as refining `~Copyable` and `~Escapable`:
12+
13+
- `Equatable`, `Copyable`, and `Hashable`
14+
- `CustomStringConvertible` and `CustomDebugStringConvertible`
15+
- `TextOutputStream` and `TextOutputStreamable`
16+
17+
`LosslessStringConvertible` will be marked as refining `~Copyable`.
18+
19+
Additionally, `Optional` and `Result` will have their `Equatable` and `Hashable` conformances updated to support `~Copyable` and `~Escapable` elements.
20+
21+
## Motivation
22+
23+
Several standard library protocols have simple requirements not involving associated types or elaborate generic implementations.
24+
25+
- `Equatable` and `Copyable` tend only to need to borrow the left- and right-hand side
26+
of their one essential operator in order to equate or compare values;
27+
- `Hashable` only needs operations to fold hash values produced by a borrowed value
28+
into a `Hasher`;
29+
- The various `String` producing or consuming operations only
30+
need to borrow their operand to turn it into a string (as `CustomStringConvertible.description`
31+
does), or can create a non-copyable values (as `LosslessStringConvertible.init?` could).
32+
33+
Use of these protocols is ubiquitous in Swift code, and this can be a major impediment to introducing
34+
non-`Copyable` types into a codebase. For example, it might be desirable to drop in a
35+
[`UniqueArray`](https://swiftpackageindex.com/apple/swift-collections/1.3.0/documentation/basiccontainers/uniquearray)
36+
to replace an `Array` in some code where the copy-on-write checks are proving prohibitively expensive. But this
37+
cannot be done if that code is relying on that array type being `Hashable`.
38+
39+
## Proposed solution
40+
41+
The following signatures in the standard library will be changed. None of these
42+
changes effect the existing semantics, just allow them to be applied to non-copyable types.
43+
44+
```
45+
protocol Equatable: ~Copyable, ~Escapable {
46+
static func == (lhs: borrowing Self, rhs: borrowing Self) -> Bool
47+
}
48+
49+
extension Equatable where Self: ~Copyable & ~Escapable {
50+
static func != (lhs: borrowing Self, rhs: borrowing Self) -> Bool
51+
}
52+
53+
protocol Comparable: Equatable, ~Copyable, ~Escapable {
54+
static func < (lhs: borrowing Self, rhs: borrowing Self) -> Bool
55+
static func <= (lhs: borrowing Self, rhs: borrowing Self) -> Bool
56+
static func >= (lhs: borrowing Self, rhs: borrowing Self) -> Bool
57+
static func > (lhs: borrowing Self, rhs: borrowing Self) -> Bool
58+
}
59+
60+
extension Comparable where Self: ~Copyable & ~Escapable {
61+
static func <= (lhs: borrowing Self, rhs: borrowing Self) -> Bool
62+
static func >= (lhs: borrowing Self, rhs: borrowing Self) -> Bool
63+
static func > (lhs: borrowing Self, rhs: borrowing Self) -> Bool
64+
}
65+
66+
protocol Hashable: Equatable & ~Copyable & ~Escapable { }
67+
68+
struct Hasher {
69+
mutating func combine<H: Hashable & ~Copyable & ~Escapable>(_ value: borrowing H)
70+
}
71+
72+
extension Optional: Equatable where Wrapped: Equatable & ~Copyable & ~Escapable {
73+
public static func ==(lhs: borrowing Wrapped?, rhs: borrowing Wrapped?) -> Bool {
74+
}
75+
76+
extension Optional: Hashable where Wrapped: Hashable & ~Copyable & ~Escapable {
77+
func hash(into hasher: inout Hasher)
78+
var hashValue: Int
79+
}
80+
81+
protocol LosslessStringConvertible: CustomStringConvertible, ~Copyable {
82+
83+
protocol TextOutputStream: ~Copyable, ~Escapable { }
84+
85+
extension TextOutputStream where Self: ~Copyable, ~Escapable { }
86+
87+
protocol CustomStringConvertible: ~Copyable, ~Escapable { }
88+
89+
protocol LosslessStringConvertible: CustomStringConvertible, ~Copyable { }
90+
91+
extension Result: Equatable where Success: Equatable & ~Copyable, Failure: Equatable {
92+
public static func ==(lhs: borrowing Self, rhs: borrowing Self) -> Bool
93+
}
94+
95+
extension DefaultStringInterpolation
96+
mutating func appendInterpolation<T>(
97+
_ value: borrowing T
98+
) where T: TextOutputStreamable & ~Copyable & ~Escapable { }
99+
100+
mutating func appendInterpolation<T>(
101+
_ value: borrowing T
102+
) where T: CustomStringConvertible & ~Copyable & ~Escapable { }
103+
}
104+
```
105+
106+
`LosslessStringConvertible` explicitly does not conform to `~Escapable` since this
107+
would require a lifetime for the created value, something that requires
108+
further language features to express.
109+
110+
Note that underscored protocol requirements and methods in extensions are omitted
111+
but will be updated as necessary.
112+
113+
## Source compatibility
114+
115+
The design of `~Copyable` and `~Escapable` explicitly allows for source compatibility with
116+
retroactive adoption, as extensions that do not restate these restrictions assume compatability.
117+
So no clients of the standard library should need to alter their existing source except
118+
with the goal of extending it to work with more types.
119+
120+
## ABI compatibility
121+
122+
As with previous retroactive adoption, the existing pre-inverse-generics features used in the
123+
standard library will be applied to preserve the same symbols as existed before.
124+
125+
The ABI implications of back deployment of these protocols is being investigated. It is hoped
126+
this can be made to work – if not, these new features may need to be gated under a minimum
127+
deployment target on ABI-stable platforms.
128+
129+
## Future directions
130+
131+
There are many other protocols that would benefit from this approach
132+
that are not included.
133+
134+
Most of these are due to the presence of associated types (for example, `RangeExpression.Bound`),
135+
which is not yet supported. Once that is a supported feature, these protocols can be
136+
similarly refined with a follow-on proposal.
137+
138+
`Codable` and `Decodable` do not have associated types – but their implementation is heavily
139+
generic, may not generalize to noncopyable types, and is out of scope for this proposal.
140+
141+
Now that these protocols support them, types such as `InlineArray` and `Span` could be made
142+
to conditionally conform to `Hashable`, as `Array` does. There is some debate to be had about
143+
the semantics of `Equatable` conformance for `Span` (though probably not for `InlineArray`),
144+
and this should be the subject of a future proposal.
145+
146+
Allowing more types to be `Custom*StringConvertible where Self: ~Copyable & ~Escapable`, such as
147+
`Optional`, requires further work on the `print` infrastructure to be able to hand such types,
148+
so is out of scope for this proposal.
149+
150+
## Alternatives considered
151+
152+
It can be argued that non-`Copyable` types have identity, and therefore should not be `Equatable`
153+
in the current sense of the protocol. In particular:
154+
155+
> Equality implies substitutability---any two instances that compare equally
156+
> can be used interchangeably in any code that depends on their values.
157+
158+
One might say that a noncopyable string type (one that does not require reference counting
159+
or copy-on-write-checking overhead) should not be considered "substitutable" for another.
160+
161+
However, the definition also states:
162+
163+
> **Equality is Separate From Identity.** The identity of a class instance is not part of an
164+
> instance's value.
165+
166+
Authors of non-`Copyable` types will need to decide for themselves whether their type should
167+
be `Equatable` and what it means. The standard library should allow it to be possible, though.
168+
169+

0 commit comments

Comments
 (0)