Skip to content

Commit 34ce90d

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

File tree

1 file changed

+158
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)