# Swift Throwing Functions (throws): Syntax, Usage, and Errors

Source: https://devdocs10.com/articles/swift/swift-throwing-functions  
By Murat Bo · Published 2026-06-01 · DevDocs10 (https://devdocs10.com)

Swift throwing functions let you write functions that can fail in a controlled, explicit way. They are a core part of Swift error handling, and you will use them whenever an operation might not succeed, such as validating input, reading data, or converting values safely.

> **Quick answer:** In Swift, you add `throws` to a function declaration to say that the function may produce an error. When calling it, you must use `try` and handle the error with `do-catch`, or use `try?` or `try!` when appropriate.

> **Difficulty:** Beginner
>
> **Helpful to know first:** You will understand this better if you already know basic Swift syntax, how functions are declared and called, and simple types like `String`, `Int`, and `Bool`.

## 1. What Is Throwing Functions (throws)?

A throwing function is a function marked with `throws` to indicate that it might fail and return an error instead of a normal result. Swift makes this explicit so you cannot accidentally ignore possible failures.

- A function with `throws` can use `throw` to send an error.
- Calling a throwing function requires `try`.
- You usually handle errors with `do-catch`.
- `throws` belongs in the function declaration, while `throw` is the statement that actually sends an error.
- This is different from optionals: optionals represent missing values, while throwing functions represent actual failure conditions that deserve explanation.

In practice, throwing functions help you separate successful results from failure cases in a clean, readable way.

## 2. Why Throwing Functions (throws) Matters

Without throwing functions, you often end up using special return values, deeply nested conditionals, or extra Boolean flags to report failure. That makes code harder to understand and easier to misuse.

Throwing functions matter because they:

- make failure explicit at the call site
- let you provide specific error information
- help avoid silent failures
- work naturally with Swift APIs and standard error handling patterns

You should use a throwing function when an operation can fail for a meaningful reason and the caller should decide how to handle that failure.

> A good rule of thumb is this: if the failure is expected and should be handled differently in different situations, `throws` is often the right tool.

## 3. Basic Syntax

### Declaring a throwing function

To declare a throwing function, place `throws` before the return type.

```swift
enum PasswordError: Error {
    case tooShort
}

func validatePassword(_ password: String) throws {
    if password.count < 8 {
        throw PasswordError.tooShort
    }
}
```

This function does not return a value, but it can still fail. If the password is too short, it throws a `PasswordError.tooShort` error.

### Calling a throwing function

When you call a throwing function, you must use `try`. The most common pattern is `do-catch`.

```swift
do {
    try validatePassword("secret")
    print("Password is valid")
} catch {
    print("Password validation failed")
}
```

Output (tested with Swift 6.4):

```text
Password validation failed
```

If the function succeeds, execution continues normally. If it throws, control moves to the `catch` block.

### throws vs throw vs try

These terms are closely related and are commonly confused:

- `throws`: marks a function as capable of throwing an error
- `throw`: actually sends an error from inside the function
- `try`: marks a call to a throwing function

```swift
enum NumberError: Error {
    case negativeValue
}

func squareRootDescription(for number: Int) throws -> String {
    if number < 0 {
        throw NumberError.negativeValue
    }

    return "Input is \(number)"
}

do {
    let message = try squareRootDescription(for: 9)
    print(message)
} catch {
    print("Could not create description")
}
```

Output (tested with Swift 6.4):

```text
Input is 9
```

This example shows all three parts working together.

## 4. Step-by-Step Examples

### Example 1: Throwing when input is invalid

This is the most common beginner example. The function checks a value and throws if the value does not meet the rules.

```swift
enum AgeError: Error {
    case tooYoung
}

func registerUser(age: Int) throws {
    if age < 18 {
        throw AgeError.tooYoung
    }

    print("Registration allowed")
}
```

The function either prints a success message or throws an error. It does not need to return a special failure value.

### Example 2: Handling specific errors in catch

You can match individual error cases and respond differently to each one.

```swift
enum LoginError: Error {
    case emptyUsername
    case emptyPassword
}

func login(username: String, password: String) throws {
    if username.isEmpty {
        throw LoginError.emptyUsername
    }

    if password.isEmpty {
        throw LoginError.emptyPassword
    }

    print("Logged in")
}

do {
    try login(username: "Taylor", password: "")
} catch LoginError.emptyUsername {
    print("Please enter a username")
} catch LoginError.emptyPassword {
    print("Please enter a password")
} catch {
    print("Unknown login error")
}
```

Output (tested with Swift 6.4):

```text
Please enter a password
```

This makes your error handling more useful than a single generic message.

### Example 3: Returning a value from a throwing function

Throwing functions can still return values when they succeed.

```swift
enum ParsingError: Error {
    case invalidNumber
}

func parseScore(from text: String) throws -> Int {
    guard let score = Int(text) else {
        throw ParsingError.invalidNumber
    }

    return score
}

do {
    let result = try parseScore(from: "42")
    print(result)
} catch {
    print("Could not parse score")
}
```

Output (tested with Swift 6.4):

```text
42
```

Here the function returns an `Int` on success, or throws on failure.

### Example 4: Using try?

Sometimes you do not care about the exact error and only want a successful value or `nil`.

```swift
let score = try? parseScore(from: "not a number")
print(score as Any)
```

`try?` converts the thrown error into an optional result. If parsing fails, `score` becomes `nil`.

### Example 5: Using rethrows

A function marked `rethrows` only throws if one of its function parameters throws. This is common in higher-order functions and utility wrappers.

```swift
func performTwice(action: () throws -> Void) rethrows {
    try action()
    try action()
}

enum ActionError: Error {
    case failed
}

do {
    try performTwice {
        throw ActionError.failed
    }
} catch {
    print("Action failed")
}
```

Output (tested with Swift 6.4):

```text
Action failed
```

The wrapper function is not inventing new errors. It only forwards errors from the closure it receives.

## 5. Practical Use Cases

- Validating user input such as email addresses, passwords, or age checks.
- Parsing strings into numbers, dates, or custom data types.
- Reading files or external data where the operation may fail.
- Building utility functions that should stop immediately when invalid data is detected.
- Writing reusable APIs where callers need detailed failure reasons.
- Creating wrapper functions around other throwing operations using `rethrows`.

## 6. Common Mistakes

### Mistake 1: Calling a throwing function without try

This is one of the most common Swift error handling mistakes. If a function is marked `throws`, Swift requires you to acknowledge that possibility at the call site.

**Problem:** This code calls a throwing function as if it were a normal function, so Swift reports an error such as `Call can throw, but it is not marked with 'try' and the error is not handled`.

```swift
let result = parseScore(from: "42")
print(result)
```

**Fix:** Add `try` and handle the error, or use `try?` if an optional result is acceptable.

```swift
do {
    let result = try parseScore(from: "42")
    print(result)
} catch {
    print("Parsing failed")
}
```

The corrected version works because the call site now explicitly handles the possibility of failure.

### Mistake 2: Using throw in a function that is not marked throws

Swift only allows `throw` inside functions that can throw. If the function declaration does not include `throws`, the compiler rejects it.

**Problem:** This function tries to throw an error without being declared as throwing, which causes a compile-time error.

```swift
enum InputError: Error {
    case empty
}

func checkInput(_ text: String) {
    if text.isEmpty {
        throw InputError.empty
    }
}
```

**Fix:** Mark the function with `throws` so Swift knows the function may send an error.

```swift
func checkInput(_ text: String) throws {
    if text.isEmpty {
        throw InputError.empty
    }
}
```

The corrected version works because the function declaration now matches its behavior.

### Mistake 3: Using try! when failure is possible

`try!` tells Swift that you are certain no error will be thrown. If an error does occur, your program crashes at runtime.

**Problem:** This code force-tries an operation that can realistically fail, so the app may terminate unexpectedly.

```swift
let score = try! parseScore(from: "hello")
print(score)
```

**Fix:** Use `do-catch` or `try?` unless you are absolutely sure the operation cannot fail.

```swift
if let score = try? parseScore(from: "hello") {
    print(score)
} else {
    print("Invalid score")
}
```

The corrected version works because the failure path is handled safely instead of crashing the program.

### Mistake 4: Confusing throws with optional returns

Beginners often try to use `throws` and optional return values for the exact same failure reason, which can make APIs unclear.

**Problem:** This function returns an optional and also throws for the same invalid input situation, so callers must deal with two overlapping failure models.

```swift
func findPositiveNumber(in text: String) throws -> Int? {
    if text.isEmpty {
        throw ParsingError.invalidNumber
    }

    return Int(text)
}
```

**Fix:** Choose one main failure model for the specific problem. If invalid input is an error, return a non-optional value and throw on failure.

```swift
func findPositiveNumber(in text: String) throws -> Int {
    guard let number = Int(text), number > 0 else {
        throw ParsingError.invalidNumber
    }

    return number
}
```

The corrected version works because the function communicates failure in one clear, consistent way.

## 7. Best Practices

### Use specific error types

Specific error types make your APIs easier to understand and easier to handle correctly. Avoid vague, catch-all errors when your code can communicate something more precise.

```swift
enum FileValidationError: Error {
    case emptyFilename
    case unsupportedExtension
}
```

This is better than using a single generic error for every failure case because callers can react differently to each case.

### Use guard for early failure

When a throwing function has required conditions, `guard` makes the failure path clear and keeps the main logic easier to read.

```swift
enum UsernameError: Error {
    case tooShort
}

func validateUsername(_ username: String) throws {
    guard username.count >= 3 else {
        throw UsernameError.tooShort
    }

    print("Username is valid")
}
```

This style reduces nesting and makes the success path stand out.

### Reserve try! for truly guaranteed success

There are rare cases where `try!` is acceptable, but only when failure would indicate a programmer mistake rather than normal runtime input.

```swift
enum StaticDataError: Error {
    case invalid
}

func loadFixedValue() throws -> Int {
    return 100
}

let value = try! loadFixedValue()
print(value)
```

Output (tested with Swift 6.4):

```text
100
```

Even here, many teams still prefer safer alternatives. Use `try!` sparingly and intentionally.

### Use throws when the caller needs the reason

If the exact reason for failure matters, throwing is often better than returning `nil`.

```swift
enum DiscountError: Error {
    case expiredCode
    case invalidCode
}

func applyDiscount(code: String) throws -> Int {
    if code == "OLD10" {
        throw DiscountError.expiredCode
    }

    guard code == "SAVE10" else {
        throw DiscountError.invalidCode
    }

    return 10
}
```

This gives the caller enough detail to show a helpful message or take a different action.

## 8. Limitations and Edge Cases

- `throws` does not replace optionals. Use optionals for absence of value, and errors for meaningful failure states.
- You cannot ignore thrown errors unless you convert them with `try?` or force them with `try!`.
- A throwing function does not have to throw every time; it only declares that it can.
- If a closure can throw, the surrounding function may also need `throws` or `rethrows`.
- `rethrows` is limited: the function may only throw when one of its function parameters throws.
- Using `try?` hides the original error information, which can make debugging harder when failures matter.
- A common “not working” case is adding `try` but forgetting to wrap the call in a context that actually handles the error, such as `do-catch` or another throwing function.

## 9. Practical Mini Project

Let’s build a small input validation function for a command-line style registration flow. It will check username and age, throw specific errors, and handle them with readable messages.

```swift
enum RegistrationError: Error {
    case emptyUsername
    case usernameTooShort
    case underage
}

func createAccount(username: String, age: Int) throws -> String {
    guard !username.isEmpty else {
        throw RegistrationError.emptyUsername
    }

    guard username.count >= 3 else {
        throw RegistrationError.usernameTooShort
    }

    guard age >= 18 else {
        throw RegistrationError.underage
    }

    return "Account created for \(username)"
}

do {
    let message = try createAccount(username: "Ava", age: 21)
    print(message)
} catch RegistrationError.emptyUsername {
    print("Please enter a username.")
} catch RegistrationError.usernameTooShort {
    print("Username must have at least 3 characters.")
} catch RegistrationError.underage {
    print("You must be at least 18 years old.")
} catch {
    print("Unexpected registration error.")
}
```

Output (tested with Swift 6.4):

```text
Account created for Ava
```

This mini project shows a realistic pattern: define an error enum, throw precise errors from validation rules, and handle each case clearly at the call site.

## 10. Key Points

- `throws` marks a function that may fail with an error.
- `throw` sends an error from inside a throwing function.
- `try` is required when calling a throwing function.
- `do-catch` is the standard way to handle thrown errors.
- `try?` converts errors into `nil`.
- `try!` crashes if an error is thrown, so use it very carefully.
- `rethrows` is used when a function only throws because one of its closure arguments throws.
- Specific error types make your code easier to understand and maintain.

## 11. Practice Exercise

Practice creating and calling your own throwing function.

- Create an enum named `TemperatureError` with a case named `belowAbsoluteZero`.
- Write a function named `checkTemperature` that takes an `Int`.
- If the temperature is less than `-273`, throw the error.
- If the value is valid, return a message string.
- Call the function using `do-catch` and print either the result or an error message.

**Expected output:** For a valid temperature, print a success message such as `Temperature is valid: 25`. For an invalid one, print a clear error message.

**Hint:** Remember that the function declaration needs `throws`, and the function call needs `try`.

```swift
enum TemperatureError: Error {
    case belowAbsoluteZero
}

func checkTemperature(_ value: Int) throws -> String {
    if value < -273 {
        throw TemperatureError.belowAbsoluteZero
    }

    return "Temperature is valid: \(value)"
}

do {
    let message = try checkTemperature(25)
    print(message)
} catch TemperatureError.belowAbsoluteZero {
    print("Temperature cannot be below absolute zero.")
} catch {
    print("Unknown temperature error.")
}
```

Output (tested with Swift 6.4):

```text
Temperature is valid: 25
```

## 12. Final Summary

Swift throwing functions give you a clear and structured way to represent operations that can fail. By adding `throws` to a function, using `throw` for failure cases, and calling the function with `try`, you make error handling visible and intentional.

In this article, you saw how to declare throwing functions, return values from them, handle errors with `do-catch`, and use alternatives like `try?`, `try!`, and `rethrows`. You also saw common mistakes, best practices, and a small working project that puts the pattern into context.

A strong next step is to learn how custom error types, `do-catch` matching, and `Result` compare to throwing functions so you can choose the best error-handling style for each Swift API you write.
