Skip to content

Commit e10a210

Browse files
airspeedswiftbenrimmingtonJnoshheckjhborla
authored
Initial draft of simple non-Copyable protocols proposal (swiftlang#3022)
* Initial draft of simple non-Copyable protocols proposal * Apply suggestions from code review Co-authored-by: Ben Rimmington <[email protected]> Co-authored-by: Janosch Hildebrand <[email protected]> * Update proposals/NNNN-make-stdlib-protocols-escapable.md Co-authored-by: Joseph Heck <[email protected]> * Rename NNNN-make-stdlib-protocols-escapable.md to NNNN-support-simple-non-copyable-protocols.md * Update NNNN-support-non-copyable-simple-protocols.md Add use case of string/bignum alternative * Update NNNN-support-non-copyable-simple-protocols.md * Assign SE-0499 and put into review. --------- Co-authored-by: Ben Rimmington <[email protected]> Co-authored-by: Janosch Hildebrand <[email protected]> Co-authored-by: Joseph Heck <[email protected]> Co-authored-by: Holly Borla <[email protected]>
1 parent 4d56d3a commit e10a210

File tree

1 file changed

+177
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)