if case let in Swift explained
Published on: April 26, 2024In Swift, we can use the case
keyword in multiple places. Most commonly, a case
is used in switched but since you’re here, you might have seen a case
in combination with an if
statement.
In this post, we’ll explore different places where we can use the case
keyword to perform something called pattern matching in Swift.
Pattern matching is a powerful feature of Swift that allows us to perform highly elegant checks to see if a given type matches a certain value.
In this post, we’ll explore a specific kind of pattern matching in Swift; the if case let
approach of pattern matching.
Understanding pattern matching
The syntax for if case let
is somewhat complex. So let’s start with a quick code sample that demonstrates how you can write an if
statement that attempts to match an enum case:
enum ShapeType {
case rectangle, triangle, circle
}
let myShape = ShapeType.rectangle
if case .rectangle = myShape {
print("myShape is a rectangle")
}
Now, let me start by saying we didn’t need to use the case
syntax here. We could have just as well written the following:
if myShape == .rectangle {
print("myShape is a rectangle")
}
However, I like the earlier example because it introduces the case
syntax in a pretty clean way.
Now, before I dig in to show you the case let
syntax I’d like to take a look at the form of pattern matching in Swift that’s most likely the one you’re most familiar with:
switch myShape {
case .rectangle:
print("myShape is a rectangle")
case .triangle:
break
case .circle:
break
}
A switch
in programming allows us to write a list of patterns that we want to compare a given value to. This is much more convenient that writing a bunch of if / else
statements.
The case
keyword in Swift does not perform any special magic. Instead, it invokes a special operator that compares our pattern (whatever we write after case
) to a value (the value we’re switching over in a switch).
So… how does that help you understand if case let
syntax?
Understanding if case let
Once you know that if case .rectangle = myShape
invokes a comparison between .rectangle
and myShape
the following suddenly makes a little more sense:
enum LoadingState {
case inProgress(Task<String, Never>)
case loaded(String)
}
let state = LoadingState.loaded("Hello, world")
if case .loaded(let string) = state {
print("Loaded string is \(string)")
}
// or
if case let .loaded(string) = state {
print("Loaded string is \(string)")
}
In both comparisons, we compare our enum case of .loaded
and we assign its associated value to a constant. I prefer case .loaded(let string)
myself because it looks a little less strange that case let .loaded(string)
but they’re functionally equivalent.
And in a switch
, you’d use the same patterns to match against which always helps me to remember:
switch state {
case .inProgress(let task):
break
case .loaded(let string):
print("Loaded string is \(string)")
}
Again, the pattern here is that we compare our case
to a value
. In a switch
this looks a lot more natural than it does in an if
statement but they’re the same under the hood and they both use the underlying ~=
comparator.
That said, writing if case .loaded(let string) = state
when you’re only interested in a single case is certainly more convenient than writing a full blown switch when you’re only interested in a single case.