File: Enums.rst

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (346 lines) | stat: -rw-r--r-- 10,093 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
:orphan:

Swift supports what type theory calls "algebraic data types", or ADTs, which
are an amalgam of two familiar C-family language features, enums and unions.
They are similar to enums in that they allow collections of independent symbolic
values to be collected into a type and switched over::

  enum Color {
    case Red, Green, Blue, Black, White
  }

  var c : Color = .Red
  switch c {
  case .Red:
    ...
  case .Green:
    ...
  case .Blue:
    ...
  }

They are also similar to C unions in that they allow a single type to
contain a value of two or more other types. Unlike C unions, however, ADTs
remember which type they contain, and can be switched over, guaranteeing that
only the currently inhabited type is ever used::

  enum Pattern {
    case Solid(Color)
    case Outline(Color)
    case Checkers(Color, Color)
  }

  var p : Pattern = .Checkers(.Black, .White)
  switch p {
  case .Solid(var c):
    print("solid \(c)")
  case .Outline(var c):
    print("outlined \(c)")
  case .Checkers(var a, var b):
    print("checkered \(a) and \(b)")
  }

Given the choice between two familiar keywords, we decided to use 'enum' to
name these types. Here are some of the reasons why:

Why 'enum'?
===========

The common case works like C
----------------------------

C programmers with no interest in learning about ADTs can use 'enum' like they
always have.

"Union" doesn't exist to Cocoa programmers
------------------------------------------

Cocoa programmers really don't think about unions at all. The frameworks vend
no public unions. If a Cocoa programmer knows what a union is, it's as a
broken C bit-bangy thing. Cocoa programmers are used to more safely
and idiomatically modeling ADTs in Objective-C as class hierarchies. The
concept of closed-hierarchy variant value types is new territory for them, so
we have some freedom in choosing how to present the feature. Trying to relate
it to C's 'union', a feature with negative connotations, is a disservice if
anything that will dissuade users from wanting to learn and take advantage of
it.

It parallels our extension of 'switch'
--------------------------------------

The idiomatic relationship between 'enum' and 'switch' in C is
well-established--If you have an enum, the best practice for consuming it is to
switch over it so the compiler can check exhaustiveness for you. We've extended
'switch' with pattern matching, another new concept for our target audience,
and one that happens to be dual to the concept of enums with payload. In the
whitepaper, we introduce pattern matching by starting from the familiar C case
of switching over an integer and gradually introduce the new capabilities of
Swift's switch. If all ADTs are 'enums', this lets us introduce both features
to C programmers organically, starting from the familiar case that looks like
C::

  enum Foo { case A, B, C, D }

  func use(_ x:Foo) {
    switch x {
    case .A:
    case .B:
    case .C:
    case .D:
    }
  }

and then introducing the parallel new concepts of payloads and patterns
together::

  enum Foo { case A, B, C, D, Other(String) }

  func use(_ x:Foo) {
    switch x {
    case .A:
    case .B:
    case .C:
    case .D:
    case .Other(var s):
    }
  }

People already use 'enum' to define ADTs, badly
-----------------------------------------------

Enums are already used and abused in C in various ways as a building block for
ADT-like types. An enum is of course the obvious choice to represented the
discriminator in a tagged-union structure. Instead of saying 'you write union
and get the enum for free', we can switch the message around: 'you write enum
and get the union for free'. Aside from that case, though, there are many uses
in C of enums as ordered integer-convertible values that are really trying to
express more complex symbolic ADTs. For example, there's the pervasive LLVM
convention of 'First_*' and 'Last_*' sigils::

  /* C */
  enum Pet {
    First_Reptile,
      Lizard = First_Reptile,
      Snake,
    Last_Reptile = Snake,

    First_Mammal,
      Cat = First_Mammal,
      Dog,
    Last_Mammal = Dog,
  };

which is really crying out for a nested ADT representation::

  // Swift
  enum Reptile { case Lizard, Snake }
  enum Mammal { case Cat, Dog }
  enum Pet {
    case Reptile(Reptile)
    case Mammal(Mammal)
  }

Or there's the common case of an identifier with standardized symbolic values
and a 'user-defined' range::

  /* C */
  enum Language : uint16_t {
    C89,
    C99,
    Cplusplus98,
    Cplusplus11,
    First_UserDefined = 0x8000,
    Last_UserDefined = 0xFFFF
  };

which again is better represented as an ADT::

  // Swift
  enum Language {
    case C89, C99, Cplusplus98, Cplusplus11
    case UserDefined(UInt16)
  }

Rust does it
------------

Rust also labels their ADTs 'enum', so there is some alignment with the
"extended family" of C-influenced modern systems programming languages in making
the same choice

Design
======

Syntax
------

The 'enum' keyword introduces an ADT (hereon called an "enum"). Within an enum,
the 'case' keyword introduces a value of the enum. This can either be a purely
symbolic case or can declare a payload type that is stored with the value::

  enum Color {
    case Red
    case Green
    case Blue
  }

  enum Optional<T> {
    case Some(T)
    case None
  }

  enum IntOrInfinity {
    case Int(Int)
    case NegInfinity
    case PosInfinity
  }

Multiple 'case' declarations may be specified in a single declaration, separated
by commas::

  enum IntOrInfinity {
    case NegInfinity, Int(Int), PosInfinity
  }

Enum declarations may also contain the same sorts of nested declarations as
structs, including nested types, methods, constructors, and properties::

  enum IntOrInfinity {
    case NegInfinity, Int(Int), PosInfinity

    constructor() {
      this = .Int(0)
    }

    func min(_ x:IntOrInfinity) -> IntOrInfinity {
      switch (self, x) {
      case (.NegInfinity, _):
      case (_, .NegInfinity):
        return .NegInfinity
      case (.Int(var a), .Int(var b)):
        return min(a, b)
      case (.Int(var a), .PosInfinity):
        return a
      case (.PosInfinity, .Int(var b)):
        return b
      }
    }
  }

They may not however contain physical properties.

Enums do not have default constructors (unless one is explicitly declared).
Enum values are constructed by referencing one of its cases, which are scoped
as if static values inside the enum type::

  var red = Color.Red
  var zero = IntOrInfinity.Int(0)
  var inf = IntOrInfinity.PosInfinity

If the enum type can be deduced from context, it can be elided and the case
can be referenced using leading dot syntax::

  var inf : IntOrInfinity = .PosInfinity
  return inf.min(.NegInfinity)

The 'RawRepresentable' protocol
-------------------------------

In the library, we define a compiler-blessed 'RawRepresentable' protocol that
models the traditional relationship between a C enum and its raw type::

  protocol RawRepresentable {
    /// The raw representation type.
    typealias RawType

    /// Convert the conforming type to its raw type.
    /// Every valid value of the conforming type should map to a unique
    /// raw value.
    func toRaw() -> RawType

    /// Convert a value of raw type to the corresponding value of the
    /// conforming type.
    /// Returns None if the raw value has no corresponding conforming type
    /// value.
    class func fromRaw(_:RawType) -> Self?
  }

Any type may manually conform to the RawRepresentable protocol following the above
invariants, regardless of whether it supports compiler derivation as underlined
below.

Deriving the 'RawRepresentable' protocol for enums
--------------------------------------------------

An enum can obtain a compiler-derived 'RawRepresentable' conformance by
declaring "inheritance" from its raw type in the following
circumstances:

- The inherited raw type must be ExpressibleByIntegerLiteral,
  ExpressibleByExtendedGraphemeClusterLiteral, ExpressibleByFloatLiteral,
  and/or ExpressibleByStringLiteral.
- None of the cases of the enum may have non-void payloads.

If an enum declares a raw type, then its cases may declare raw
values. raw values must be integer, float, character, or string
literals, and must be unique within the enum. If the raw type is
ExpressibleByIntegerLiteral, then the raw values default to
auto-incrementing integer literal values starting from '0', as in C. If the
raw type is not ExpressibleByIntegerLiteral, the raw values must
all be explicitly declared::

  enum Color : Int {
    case Black   // = 0
    case Cyan    // = 1
    case Magenta // = 2
    case White   // = 3
  }

  enum Signal : Int32 {
    case SIGKILL = 9, SIGSEGV = 11
  }

  enum NSChangeDictionaryKey : String {
    // All raw values are required because String is not
    // ExpressibleByIntegerLiteral
    case NSKeyValueChangeKindKey = "NSKeyValueChangeKindKey"
    case NSKeyValueChangeNewKey = "NSKeyValueChangeNewKey"
    case NSKeyValueChangeOldKey = "NSKeyValueChangeOldKey"
  }

The compiler, on seeing a valid raw type for an enum, derives a RawRepresentable
conformance, using 'switch' to implement the fromRaw and toRaw
methods. The NSChangeDictionaryKey definition behaves as if defined::

  enum NSChangeDictionaryKey : RawRepresentable {
    typealias RawType = String

    case NSKeyValueChangeKindKey
    case NSKeyValueChangeNewKey
    case NSKeyValueChangeOldKey

    func toRaw() -> String {
      switch self {
      case .NSKeyValueChangeKindKey:
        return "NSKeyValueChangeKindKey"
      case .NSKeyValueChangeNewKey:
        return "NSKeyValueChangeNewKey"
      case .NSKeyValueChangeOldKey:
        return "NSKeyValueChangeOldKey"
      }
    }

    static func fromRaw(_ s:String) -> NSChangeDictionaryKey? {
      switch s {
      case "NSKeyValueChangeKindKey":
        return .NSKeyValueChangeKindKey
      case "NSKeyValueChangeNewKey":
        return .NSKeyValueChangeNewKey
      case "NSKeyValueChangeOldKey":
        return .NSKeyValueChangeOldKey
      default:
        return nil
      }
    }
  }