Difference between switch cases "@unknown default" and "default" in Swift 5 Difference between switch cases "@unknown default" and "default" in Swift 5 swift swift

Difference between switch cases "@unknown default" and "default" in Swift 5


From SE-0192: Handling Future Enum Cases (emphasis mine):

When switching over a non-frozen enum, the switch statement that matches against it must include a catch-all case (usually default or an "ignore" _ pattern).

switch excuse {case .eatenByPet:  // …case .thoughtItWasDueNextWeek:  // …}

Failure to do so will produce a warning in Swift 5. A program will trap at run time if an unknown enum case is actually encountered.

All other uses of enums (if case, creation, accessing members, etc) do not change. Only the exhaustiveness checking of switches is affected by the frozen/non-frozen distinction. Non-exhaustive switches over frozen enums (and boolean values) will continue to be invalid in all language modes.

Here's a more complicated example:

switch (excuse, notifiedTeacherBeforeDeadline) {case (.eatenByPet, true):  // …case (.thoughtItWasDueNextWeek, true):  // …case (_, false):  // …}

This switch handles all known patterns, but still doesn't account for the possibility of a new enum case when the second tuple element is true. This should result in a warning in Swift 5, like the first example.

@unknown

The downside of using a default case is that the compiler can no longer alert a developer that a particular enum has elements that aren't explicitly handled in the switch. To remedy this, switch cases will gain a new attribute, @unknown.

switch excuse {case .eatenByPet:  // …case .thoughtItWasDueNextWeek:  // …@unknown default:  // …}

Like the regular default, @unknown default matches any value; it is a "catch-all" case. However, the compiler will produce a warning if all known elements of the enum have not already been matched. This is a warning rather than an error so that adding new elements to the enum remains a source-compatible change. (This is also why @unknown default matches any value rather than just those not seen at compile-time.)

@unknown may only be applied to default or a case consisting of the single pattern _. Even in the latter case, @unknown must be used with the last case in a switch. This restriction is discussed further in the "unknown patterns" section under "Future directions".

The compiler will warn if all enums in the pattern being matched by @unknown are explicitly annotated as frozen, or if there are no enums in the pattern at all. This is a warning rather than an error so that annotating an enum as frozen remains a source-compatible change. If the pattern contains any enums that are implicitly frozen (i.e. because it is a user-defined Swift enum), @unknown is permitted, in order to make it easier to adapt to newly-added cases.

@unknown has a downside that it is not testable, since there is no way to create an enum value that does not match any known cases, and there wouldn't be a safe way to use it if there was one. However, combining @unknown with other cases using fallthrough can get the effect of following another case's behavior while still getting compiler warnings for new cases.

switch excuse {case .eatenByPet:  showCutePicturesOfPet()case .thoughtItWasDueNextWeek:  fallthrough@unknown default:  askForDueDateExtension()}


In the case of only using default, it's used as when our switch doesn't match any of the options. Let's see a first exhaustive case:

enum Option {  case A  case B}func optionSelected(option: Option) {  switch(option) {    case .A:      print("You chose A!")    case .B:      print("You chose B!")  }}

This example is exhaustive and we won't get any error. But what if we need to add options in our enum?

enum Option {  case A  case B  case C}func optionSelected(option: Option) {  switch(option) {    case .A:      print("You chose A!")    case .B:      print("You chose B!")  }}

In this second example, we will get an error Switch must be exhaustive. To avoid this error, we might implement a default case:

enum Option {  case A  case B  case C}func optionSelected(option: Option) {  switch(option) {    case .A:      print("You chose A!")    case .B:      print("You chose B!")    default:      print("You chose other option!")  }}

If the user chose, Option C, he will fall into the default case. But what happens when we add an Option D, E, etc. into the Enum? If we don't change the switch they will all fall into default. This might not be a problem, depending on what you want to implement.

Now, with the @unknown, we continue catching all the other options, but the difference here is that the compiler we issue a warning Switch must be exhaustive (not an error!) if all known elements of the enum haven't been matched (i.e. the switch wasn't exhaustive).

enum Option2 {  case A  case B  case C}func optionSelected2(option: Option2) {  switch(option) {    case .A:      print("You chose A!")    case .B:      print("You chose B!")    case .C:      print("You chose C!")    @unknown default:      print("You chose other option!")  }}

If we add an option D, E, etc, we will just see a warning and then decide if we want to implement the other cases (for example, we want a custom message for option D and E) or if we will just leave the default message "you chose another option". Think of it as a friendly remainder instead of a big red error :)

Other examples: https://www.raywenderlich.com/55728-what-s-new-in-swift-5


The answers that imply that you will ever get a warning for your enums are wrong. This is about how Swift treats C (and Objective-C) enums in an external library/framework. A few Swift standard library enums are affected.

Okay, so let's consider an actual example. We write an exhaustive switch against a Cocoa enum:

    var err : [URLError.NetworkUnavailableReason] = ...    switch err {    case URLError.NetworkUnavailableReason.cellular: break    case URLError.NetworkUnavailableReason.expensive: break    case URLError.NetworkUnavailableReason.constrained: break    }

At this point we get a warning. Why?

Well, our switch is exhaustive now, but it might not always be exhaustive. What if the framework adds cases later? Our compiled code won't change, and so it will crash (trap) when the new case arrives into the switch.

So we need a way to allow our code to keep working even if the framework changes. The compiler therefore is telling us: "Add a default case, even though the switch is exhaustive."

Now, of course it would be possible to add an ordinary default case:

    switch err {    case URLError.NetworkUnavailableReason.cellular: break    case URLError.NetworkUnavailableReason.expensive: break    case URLError.NetworkUnavailableReason.constrained: break    default: break    }

The problem with that is if the framework does change, we'll never hear about it. So there's a better way, @unknown default:

    switch err {    case URLError.NetworkUnavailableReason.cellular: break    case URLError.NetworkUnavailableReason.expensive: break    case URLError.NetworkUnavailableReason.constrained: break    @unknown default: break    }

This means: "Hey, compiler, I don't expect there to be any more cases, but if I ever try to compile this project against the framework and you discover that there is another case, warn me so that I can add it explicitly to my switch."

So that's what's special about @unknown. If another case is added behind our backs, the compiler will give us another warning telling us about it, and we can fix our code to include it. In other words, you obey the warning now to get rid of the warning now in exchange for a possible useful warning in the future.

Another nice thing about this syntax is that if we add an @unknown default to a switch that is not exhaustive now, the compiler will warn us about that.