Swift Typed Throws
Typed throws is a declaration that names the one error type a function can throw. The caller can then handle that error as its exact type instead of a general error.
A typed function and a typed catch
The loadScore function declares throws(LoadError), so only LoadError values can come out of it. The empty name throws the missing case and the switch in the catch prints its line.
enum LoadError: Error {
case missing
case unreadable
}
func loadScore(_ name: String) throws(LoadError) -> Int {
if name.isEmpty {
throw .missing
}
if name == "locked" {
throw .unreadable
}
return 42
}
do {
let score = try loadScore("Ana")
print(score)
} catch {
switch error {
case .missing:
print("Missing name")
case .unreadable:
print("Unreadable record")
}
}
do {
let score = try loadScore("")
print(score)
} catch {
switch error {
case .missing:
print("Missing name")
case .unreadable:
print("Unreadable record")
}
}An exhaustive switch over the typed error
Both LoadError cases appear in the switch and no default branch is needed. The locked name lands on unreadable and the empty name lands on missing.
enum LoadError: Error {
case missing
case unreadable
}
func loadScore(_ name: String) throws(LoadError) -> Int {
if name.isEmpty {
throw .missing
}
if name == "locked" {
throw .unreadable
}
return 42
}
do {
let score = try loadScore("locked")
print(score)
} catch {
switch error {
case .missing:
print("Missing name")
case .unreadable:
print("Unreadable record")
}
}
do {
let score = try loadScore("")
print(score)
} catch {
switch error {
case .missing:
print("Missing name")
case .unreadable:
print("Unreadable record")
}
}Passing a typed error onward
The firstScore function declares the same typed throws, so the LoadError from loadScore passes straight through it. The caller switches over that same typed error.
enum LoadError: Error {
case missing
case unreadable
}
func loadScore(_ name: String) throws(LoadError) -> Int {
if name.isEmpty {
throw .missing
}
if name == "locked" {
throw .unreadable
}
return 42
}
func firstScore(_ names: [String]) throws(LoadError) -> Int {
return try loadScore(names[0])
}
do {
let score = try firstScore(["Ana", "Bo"])
print(score)
} catch {
switch error {
case .missing:
print("Missing name")
case .unreadable:
print("Unreadable record")
}
}
do {
let score = try firstScore(["", "Bo"])
print(score)
} catch {
switch error {
case .missing:
print("Missing name")
case .unreadable:
print("Unreadable record")
}
}A second typed error for a range check
The checkedPercent function throws a RangeError outside the range from 0 to 100. Each catch switches over the typed error and prints the matching line.
enum RangeError: Error {
case tooLow
case tooHigh
}
func checkedPercent(_ value: Int) throws(RangeError) -> Int {
if value < 0 {
throw .tooLow
}
if value > 100 {
throw .tooHigh
}
return value
}
do {
let value = try checkedPercent(50)
print(value)
} catch {
switch error {
case .tooLow:
print("Too low")
case .tooHigh:
print("Too high")
}
}
do {
let value = try checkedPercent(150)
print(value)
} catch {
switch error {
case .tooLow:
print("Too low")
case .tooHigh:
print("Too high")
}
}
do {
let value = try checkedPercent(-3)
print(value)
} catch {
switch error {
case .tooLow:
print("Too low")
case .tooHigh:
print("Too high")
}
}Example
enum LoadError: Error {
case missing
case unreadable
}
func loadScore(_ name: String) throws(LoadError) -> Int {
if name.isEmpty {
throw .missing
}
if name == "locked" {
throw .unreadable
}
return 42
}
func report(_ name: String) throws(LoadError) -> String {
let score = try loadScore(name)
return "\(name) scored \(score)"
}
do {
let line = try report("Ana")
print(line)
} catch {
switch error {
case .missing:
print("Missing name")
case .unreadable:
print("Unreadable record")
}
}
do {
let line = try report("locked")
print(line)
} catch {
switch error {
case .missing:
print("Missing name")
case .unreadable:
print("Unreadable record")
}
}
do {
let line = try report("")
print(line)
} catch {
switch error {
case .missing:
print("Missing name")
case .unreadable:
print("Unreadable record")
}
}Key points
- Typed throws writes the error type in parentheses in the declaration, as throws(LoadError).
- The caller knows the exact error type before writing any catch code.
- Inside a catch block for a typed call, the error value already has that type.
- A switch over that error can list every case with no default branch.
- A function can pass a typed error onward by declaring the same typed throws.
In this example, the report function declares throws(LoadError) and calls loadScore inside. The call for Ana succeeds and prints her scored line. The call for the locked name throws unreadable inside loadScore and the error passes through report unchanged. Each catch switches over the error directly, because the error already has the LoadError type, and the last call lands on the missing case.
Note
Typed throws suits a function whose failures all come from one error type.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
In the catch block of a call to a function declared throws(LoadError), how is the error handled most directly?