Understanding Swift’s @unknown and @frozen attribute

Vijay Chandran Jayachandran
3 min readOct 24, 2021

TLDR:

It is for when you are interested in:
• compiler optimization.
• Generate compile error in-case a new case/property is added.

Applicable to enum and struct constructs.

Targeted at library authors.

NOTE: @frozen is only applicable when developing on something called as Library Evolution mode, introduced in Swift 5.0 onwards. Else, all structs and enums are @frozen by default.

HTWR:

@Unknown:
Introduced in Swift 5, https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md,
The Unknown attribute was introduced to aid nuanced programmers who are obsessed with handling every case, as well as to field possible unexpected executions.

Example:

consider an enum:

enum MyType {
case good
case neutral
}

A diligent programmer would want to handle every possible case:

// type is an instance of MyType
switch type {
case .good:
print(“Much appreciated”)
case .neutral:
print(“Okay, good”)
}

Now suppose someone introduces a new case in MyType:

enum MyType {
case veryGood
case good
case neutral // <- new case
}

Now, this would cause an error in the code that handle this enum


switch type { // ERROR… “Switch must be exhaustive”
case .veryGood:
sprint(“Come on in!”)
case .good:
print(“Come on in!”)
}

Now one could go about avoiding this error by using the default for the case, but that would handle ALL unfactored cases — this may introduce unexpected bugs, and something the aforementioned diligent programmer would avoid doing:

switch type {
case .veryGood:
print(“Come on in!”)
case .good:
print(“Come on in!”)
default: /* <- This fixes the error, and avoids any cases added in the future. */
print(“Pass”)
}

But what If we want to particularly handle a certain case that is added in the future?

enum MyType {
case veryGood
case good
case neutral
case pureEvil /* <- New case, which we may want to handle */
}

But since we used default, the newly added type .pureEvil would go un-noticed.

So how do we make the compiler warn us that a new case is not factored in our switch case (in future when a new case is added), yet avoid the default case?
We use the @unknown default, rather than just default:


switch type {
case .veryGood:
print(“Come on in!”)
case .good:
print(“Come on in!”)
@unknown default: /* The code compiles now, but the compiler will warn that this switch condition does not handle all cases */
print(“Pass”)
}

This allows us to write code that handles all the cases currently in a given enum, AND also provide a sort of a default case, where the compiler will not warn us that “Default will never be executed”.

If a case is added to the enum, only then will will the compiler warn us “Switch must be exhaustive”.

To avoid the compiler warning when using a non-frozen type (structs/enums, before Swift 5) we add the @frozen attribute to the type.

@frozen:
As said earlier, this attribute is used if you are a framework developer, in the Library Evolution mode.

It is like a contract that the framework developer makes with the compiler in that mode, stating that “No further cases will be added to this enum/struct in the future”.

This means that users of your framework need not add the @default attribute, since no new cases can be added to the frozen enum, in the future releases of the framework.

Thanks for reading!

--

--