1
1
# ` @noncopyable ` structs and enums
2
2
3
3
* 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 )
5
5
* Review Manager: TBD
6
6
* Status: ** Partially implemented** as ` @_moveOnly ` with ` -enable-experimental-move-only `
7
7
@@ -116,7 +116,7 @@ class SharedFile {
116
116
117
117
### Restrictions on use in generics
118
118
119
- Noncopyable types may have generic parameters:
119
+ Noncopyable types may have generic type parameters:
120
120
121
121
``` swift
122
122
// A type that reads from a file descriptor consisting of binary values of type T
@@ -127,20 +127,103 @@ struct TypedFile<T> {
127
127
128
128
func read () -> T { ... }
129
129
}
130
+
131
+ let byteFile: TypedFile<UInt8 > // OK
130
132
```
131
133
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:
137
150
138
151
``` swift
139
152
// ERROR: Cannot use noncopyable type FileDescriptor in generic type Optional
140
153
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. The trade-off in doing this is the loss of static guarantees
187
+ about the lifetime of the value, since the class reference can be copied.
188
+
189
+ To transfer the noncopyable value in and out of the wrapper class instance,
190
+ introduce a concrete noncopyable enum to represent the case where the value
191
+ was taken out of the instance:
192
+
193
+ ``` swift
194
+ @_moveOnly
195
+ enum MaybeFileDescriptor {
196
+ case just (FileDescriptor)
197
+ case none
198
+ }
199
+
200
+ class WrappedFile {
201
+ var file: MaybeFileDescriptor
202
+
203
+ enum Err : Error { case noFile }
204
+
205
+ init (_ fd : take FileDescriptor) {
206
+ file = .just (fd)
207
+ }
208
+
209
+ func consume () throws -> FileDescriptor {
210
+ if case let .just (fd) = file { // consume `self.file`
211
+ file = .none // must reinitialize `self.file` before returning
212
+ return fd
213
+ }
214
+ throw Err.noFile
215
+ }
216
+ }
217
+
218
+ func example (_ fd1 : take FileDescriptor,
219
+ _ fd2 : take FileDescriptor) -> [WrappedFile] {
220
+ // create an array of descriptors
221
+ return [WrappedFile (fd1), WrappedFile (fd2)]
222
+ }
141
223
```
142
224
143
- (more examples here)
225
+ All of this boilerplate melts away once noncopyable types support generics.
226
+
144
227
145
228
### Using values of ` @noncopyable ` type
146
229
0 commit comments