`diff Swift C++`: `switch` on steroids
switch
statement in C++, C# or Java is pretty much what we have had since the beginning of the 70s. Java and C# improved it a bit by disallowing automatic fallthrough between case
statements and support of string values for matching.
But Swift greatly improves switch
:
- Implicit fallthroughs between
case
bodies are not disallowed - they don't exist. There is no compiler error if you forget to putbreak
. Instead the compiler will do it for you anyway. - Taking a further step in the right direction if you want to enable fallthroughs between
case
statements, you have to do it explicitly by usingfallthrough
keybword. - All
switch
statements must be exhaustive - that is they must be able to handle entire field of states of the value on which we are switching. - Each
case
must contain at least one executable statement which means that all cases will at least do something, and if you don't want them to do anything you will have to explicitly do so. - To specify more than one statement for a single switch you can separate them with commas. This is a nice syntatic improvement but also a necessary consequence of #4.
- You can do range matching by using
..
(semi-open) and...
(closed) interval operators. Last but not least, apart from allowing integers, strings and floating point values in
case
, you can also do switching on tuples. Tuples switching allows:- Matching wildcards by using
_
identifier and combining values and ranges within tuple values - Binding values from tuples in the
case
statement itself which allows you to use them immediately without having to query the tuple again - Using opotional
where
clause for additional conditions on tuple bound values. - Matching of
enum
values and their associated value tuples.
- Matching wildcards by using
As you can see there is really a very tiny overlap between a valid switch
statement in say C++ and a valid switch
statment in Swift.
As a really small illustration, let's consider the example from my enum
post. There I had an IpAddress
enum
representing either IPv4 or IPv6 addresses. It adopts Printable
protocol
from standard Swift library. I rewrote the implementation here to show no-implicit-fallthrough and no-break-needed:
// Implementation of Printable protocol
var description: String {
var desc: String
switch self {
case let .v4(b1, b2, b3, b4):
desc = "\(b1).\(b2).\(b3).\(b4)"
case let .v6(w1, w2, w3, w4, w5, w6, w7, w8):
desc = "\(w1):\(w2):\(w3):\(w4):\(w5):\(w6):\(w7):\(w8)"
}
return desc
}
Here you can see (if you execute the code that is):
- no-implicit-fallthrough rule
- no-break-needed rule
- exhaust-all-values rule (only
v4
andv6
are handled but that's all there is to thisenum
otherwise compiler would issue an error) - pattern matching on
enum
values (v4
andv6
) and their associated tuples.
Let's add a special case: we want to print ::1
when IPv6 loopback address is specified. With tuple pattern matching we can easily handle this:
// Implementation of Printable protocol
var description: String {
var desc: String
switch self {
case let .v4(b1, b2, b3, b4):
desc = "\(b1).\(b2).\(b3).\(b4)"
// Specialized case has to appear before the generalized v6 case
case let .v6(0, 0, 0, 0, 0, 0, 0, 1):
desc = "::1"
case let .v6(w1, w2, w3, w4, w5, w6, w7, w8):
desc = "\(w1):\(w2):\(w3):\(w4):\(w5):\(w6):\(w7):\(w8)"
}
return desc
}
One important thing to note in the code above is that the special case has to appear before the general case. This is because switch
will evaluate patterns in the order they were specified. This is reasonable and not at all surprising but it is something to keep in mind.