Skip to content

Commit 10263e9

Browse files
committed
fixes based on feedback
1 parent bbcb22a commit 10263e9

File tree

1 file changed

+92
-8
lines changed

1 file changed

+92
-8
lines changed

Diff for: proposals/NNNN-noncopyable-structs-and-enums.md

+92-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# `@noncopyable` structs and enums
22

33
* Proposal: [SE-NNNN](NNNN-noncopyable-structs-and-enums.md)
4-
* Authors: [Joe Groff](https://github.com/jckarter), [Michael Gottesman](https://github.com/gottesmm), [Andrew Trick](https://github.com/atrick)
4+
* Authors: [Joe Groff](https://github.com/jckarter), [Michael Gottesman](https://github.com/gottesmm), [Andrew Trick](https://github.com/atrick), [Kavon Farvardin](https://github.com/kavon)
55
* Review Manager: TBD
66
* Status: **Partially implemented** as `@_moveOnly` with `-enable-experimental-move-only`
77

@@ -116,7 +116,7 @@ class SharedFile {
116116

117117
### Restrictions on use in generics
118118

119-
Noncopyable types may have generic parameters:
119+
Noncopyable types may have generic type parameters:
120120

121121
```swift
122122
// A type that reads from a file descriptor consisting of binary values of type T
@@ -127,20 +127,104 @@ struct TypedFile<T> {
127127

128128
func read() -> T { ... }
129129
}
130+
131+
let byteFile: TypedFile<UInt8> // OK
130132
```
131133

132-
However, at this time, noncopyable types are not allowed to conform to
133-
protocols, and they cannot be used as type arguments when instantiating
134-
generic types or calling generic functions. A value of noncopyable type also
135-
cannot be stored inside of an `Any` or other existential.
136-
(Lifting these limitations is discussed under Future Directions.)
134+
However, at this time, noncopyable types themselves are not allowed to be used
135+
as a generic type. This means a noncopyable type _cannot_:
136+
137+
- conform to protocols, like `Error` or `Sendable`.
138+
- serve as a type witness for an `associatedtype` requirement.
139+
- be used as a type argument when instantiating generic types or calling generic functions.
140+
- be cast to `Any` or any other existential.
141+
- be accessed through reflection.
142+
- appear in a tuple.
143+
144+
The reasons for these restrictions and ways of lifting them are discussed under
145+
Future Directions.
146+
147+
Since a good portion of Swift's standard library rely on generics, there are a
148+
a number of common types and functions that will not work with today's
149+
noncopyable types:
137150

138151
```swift
139152
// ERROR: Cannot use noncopyable type FileDescriptor in generic type Optional
140153
let x = Optional(FileDescriptor(open("/etc/passwd", O_RDONLY)))
154+
155+
// ERROR: Cannot use noncopyable type FileDescriptor in generic type Array
156+
let fds: [FileDescriptor] = []
157+
158+
// ERROR: Cannot use noncopyable type FileDescriptor in generic type Any
159+
print(FileDescriptor(-1))
160+
161+
// ERROR: Noncopyable struct SocketEvent cannot conform to Error
162+
@noncopyable enum SocketEvent: Error {
163+
case requestedDisconnect(SocketPair)
164+
}
165+
```
166+
167+
For example, the `print` function expects to be able to convert its argument to
168+
`Any`, which is a copyable value. Internally, it also relies on either
169+
reflection or conformance to `CustomStringConvertible`. Since a noncopyable type
170+
can't do any of those, a suggested workaround is to explicitly define a
171+
conversion to `String`:
172+
173+
```swift
174+
extension FileDescriptor /*: CustomStringConvertible */ {
175+
var description: String {
176+
return "file descriptor #\(fd)"
177+
}
178+
}
179+
180+
let fd = FileDescriptor(-1)
181+
print(fd.description)
182+
```
183+
184+
A more general kind of workaround to mix generics and noncopyable types
185+
is to wrap the value in an ordinary class instance, which itself can participate
186+
in generics. To transfer the noncopyable value in or out of the wrapper class
187+
instance, using `Optional<FileDescriptor>` for the class's field would be
188+
ideal. But until that is supported, a concrete noncopyable enum can represent
189+
the case where the value of interest was taken out of the instance:
190+
191+
```swift
192+
@noncopyable
193+
enum MaybeFileDescriptor {
194+
case some(FileDescriptor)
195+
case none
196+
}
197+
198+
class WrappedFile {
199+
var file: MaybeFileDescriptor
200+
201+
enum Err: Error { case noFile }
202+
203+
init(_ fd: consuming FileDescriptor) {
204+
file = .some(fd)
205+
}
206+
207+
func consume() throws -> FileDescriptor {
208+
if case let .some(fd) = file { // consume `self.file`
209+
file = .none // must reinitialize `self.file` before returning
210+
return fd
211+
}
212+
throw Err.noFile
213+
}
214+
}
215+
216+
func example(_ fd1: consuming FileDescriptor,
217+
_ fd2: consuming FileDescriptor) -> [WrappedFile] {
218+
// create an array of descriptors
219+
return [WrappedFile(fd1), WrappedFile(fd2)]
220+
}
141221
```
142222

143-
(more examples here)
223+
All of this boilerplate melts away once noncopyable types support generics.
224+
Even before then, one major improvement would be to eliminate the need to define
225+
types like `MaybeFileDescriptor` through a noncopyable `Optional`
226+
(see Future Directions).
227+
144228

145229
### Using values of `@noncopyable` type
146230

0 commit comments

Comments
 (0)