Swift @discardableResult: Ignore Function Return Values Safely

@discardableResult is a Swift function attribute that tells the compiler it is acceptable to call a function and ignore the value it returns. This matters because Swift normally warns you when a function returns something useful and you do not use it. Understanding this attribute helps you design clearer APIs and avoid warnings for functions that are sometimes used for their result and sometimes called only for their side effects.

Quick answer: Use @discardableResult before a function or method declaration when callers may legitimately ignore its return value. It does not change what the function returns; it only suppresses the compiler warning about an unused result.

Difficulty: Beginner

Helpful to know first: You'll understand this better if you know basic Swift syntax, how functions return values, and why the compiler shows warnings for suspicious code.

1. What Is @discardableResult?

@discardableResult is an attribute you place on a function, method, or subscript declaration. It tells Swift that callers are allowed to ignore the returned value without receiving the usual unused-result warning.

Without this attribute, Swift often warns when returned values are ignored because that can indicate a bug. For example, if a function computes something important and the caller throws the result away, the compiler wants your attention. But some functions return a value only as a convenience, not as a requirement. That is where @discardableResult is useful.

A common comparison is @discardableResult versus returning Void. Returning Void means there is no result to use. Using @discardableResult means there is a result, but callers are not forced to keep it.

2. Why @discardableResult Matters

Swift warnings are designed to catch mistakes early. If a function returns a value, ignoring it may mean you forgot to assign it, print it, or pass it along. That warning is often helpful.

However, some APIs are intentionally flexible:

In those cases, forcing every caller to use _ = ... just to silence warnings makes code noisy. @discardableResult lets the API express intent more clearly.

You should not use it just to hide a poor API design. If the return value is important for correctness, callers should see a warning when they ignore it.

3. Basic Syntax or Core Idea

Declaring a function with @discardableResult

The attribute goes on the line above the declaration.

@discardableResult
func save(name: String) -> Bool {
    print("Saved \(name)")
    return true
}

This function still returns a Bool. The attribute simply says that ignoring that Bool is acceptable.

Calling it and ignoring the result

Because the function is marked with @discardableResult, this call is allowed without an unused-result warning.

save(name: "Report.txt")

The side effect happens, and the returned success value is discarded.

Calling it and using the result

You can still use the returned value normally.

let didSave = save(name: "Notes.txt")
print("Success: \(didSave)")

This is the key idea: the return value remains available, but using it is optional.

4. Step-by-Step Examples

Example 1: A function that logs and returns a message

This example shows a function with a useful side effect and a useful result. Some callers may care about the returned string, while others may not.

@discardableResult
func logMessage(_ message: String) -> String {
    print("LOG: \(message)")
    return message
}

logMessage("App started")

let savedMessage = logMessage("User signed in")
print(savedMessage)

The first call ignores the return value. The second call stores it. Both are valid because the function explicitly allows discarded results.

Example 2: A mutating method that returns self for chaining

Builder-style APIs often return the modified instance so calls can be chained. But callers may also want to call the method only for its effect.

struct Counter {
    var value = 0

    @discardableResult
    mutating func increment() -> Int {
        value += 1
        return value
    }
}

var counter = Counter()
counter.increment()

let newValue = counter.increment()
print(newValue)

The method updates the counter either way. Returning the new value is a convenience, not a requirement.

Example 3: Comparing with a normal function that returns a value

Now compare that behavior with a function that does not use @discardableResult. Ignoring the result is more suspicious here.

func square(_ number: Int) -> Int {
    return number * number
}

let result = square(4)
print(result)

This kind of function exists mainly to compute and return a value. If a caller ignores that value, the warning is useful, so adding @discardableResult would usually be the wrong choice.

Example 4: A method that returns self

Returning self is common when you want optional chaining-like fluency in your own API design.

struct TextBuilder {
    var text = ""

    @discardableResult
    mutating func append(_ value: String) -> TextBuilder {
        text += value
        return self
    }
}

var builder = TextBuilder()
builder.append("Hello")
builder.append(", world")
print(builder.text)

Here the returned builder is optional to use. The real purpose may simply be updating the stored text.

5. Practical Use Cases

If the returned value is central to the function's purpose, do not use this attribute just to reduce warnings.

6. Common Mistakes

Mistake 1: Adding @discardableResult to a pure calculation function

Some developers add @discardableResult everywhere to make warnings disappear. That hides useful feedback from the compiler.

Problem: This function exists to calculate and return a value. Ignoring that result is probably a bug, so suppressing the warning weakens the API.

@discardableResult
func multiply(_ a: Int, _ b: Int) -> Int {
    return a * b
}

multiply(3, 4)

Fix: Remove the attribute when the return value is the main point of the function.

func multiply(_ a: Int, _ b: Int) -> Int {
    return a * b
}

let product = multiply(3, 4)
print(product)

The corrected version preserves the compiler's warning behavior and makes the intended usage clear.

Mistake 2: Thinking the attribute changes the return type

@discardableResult does not convert a function into a Void-returning function. The value is still returned and can still be captured.

Problem: This code assumes the method no longer returns anything, which can lead to API confusion and incorrect documentation.

struct ScoreTracker {
    var score = 0

    @discardableResult
    mutating func add(_ points: Int) -> Int {
        score += points
        return score
    }
}

Fix: Treat the attribute as permission to ignore the result, not as removal of the result.

var tracker = ScoreTracker()
tracker.add(10) // allowed to ignore

let currentScore = tracker.add(5)
print(currentScore)

The corrected version shows that the method still returns an Int; ignoring it is simply optional.

Mistake 3: Using @discardableResult to hide an API design problem

Sometimes a function returns a value because the design is trying to serve too many purposes at once. Adding the attribute may hide that confusion instead of solving it.

Problem: This function both updates data and returns a status string, but the return value may not be meaningful enough to justify returning it at all.

@discardableResult
func updateSettings() -> String {
    print("Settings updated")
    return "Done"
}

Fix: If callers rarely need the result, consider returning Void instead of using @discardableResult.

func updateSettings() {
    print("Settings updated")
}

The corrected version is clearer because the function now communicates that the side effect is its real purpose.

7. Best Practices

Use it only when ignoring the result is genuinely valid

If both of these usages make sense, the attribute is a good fit: using the result or ignoring it.

@discardableResult
func cache(_ value: String) -> Bool {
    print("Cached: \(value)")
    return true
}

This works well because storing the value is the main effect, while the success flag is a helpful extra.

Prefer clear API design over warning suppression

If the result is not useful, returning Void is better than returning a throwaway value with @discardableResult.

// Less clear
@discardableResult
func startSession() -> String {
    return "Started"
}

// Preferred when the returned value adds little meaning
func startSession() {
    print("Session started")
}

The preferred version tells callers exactly what to expect and reduces unnecessary API complexity.

Document why the return value can be ignored

This attribute is more useful when the reason is obvious from the function name, return type, and behavior.

struct DownloadTracker {
    var completed = 0

    @discardableResult
    mutating func markComplete() -> Int {
        completed += 1
        return completed
    }
}

Here the name suggests that the side effect is primary, while the returned count is a convenience.

8. Limitations and Edge Cases

If you only want to silence a warning at one call site, using _ = someFunction() can be clearer than changing the function declaration for every caller.

9. Practical Mini Project

This mini project creates a simple notes store. The addNote method updates stored data and returns the new count. Some callers use that count, while others simply add a note.

struct NotesStore {
    private(set) var notes: [String] = []

    @discardableResult
    mutating func addNote(_ text: String) -> Int {
        notes.append(text)
        return notes.count
    }

    func listNotes() {
        for note in notes {
            print("- \(note)")
        }
    }
}

var store = NotesStore()

store.addNote("Buy milk")

let totalNotes = store.addNote("Call Alex")
print("Total notes: \(totalNotes)")

store.listNotes()

This is a good use of @discardableResult because adding the note is the main action, while the new count is useful but optional.

10. Key Points

11. Practice Exercise

Create a BankAccount type with a method named deposit. The method should add money to the balance and return the new balance. Mark it so callers can ignore the returned value when they only care about updating the account.

Expected output: The final printed value should show the updated balance after the second deposit.

Hint: Use @discardableResult above the method declaration, not at the call site.

struct BankAccount {
    var balance: Double = 0.0

    @discardableResult
    mutating func deposit(_ amount: Double) -> Double {
        balance += amount
        return balance
    }
}

var account = BankAccount()
account.deposit(50.0)

let updatedBalance = account.deposit(25.0)
print("Updated balance: \(updatedBalance)")

12. Final Summary

@discardableResult is a small Swift attribute, but it has an important role in API design. It tells the compiler that a returned value may be ignored intentionally, which helps when a function's main purpose is a side effect and the returned value is only a convenience. Used well, it makes call sites cleaner and reduces unnecessary warning suppression code.

The key is moderation. Do not add @discardableResult to every function that returns something. If the result is essential, let Swift warn when it is ignored. If the result is optional and the action itself is the main reason the function exists, the attribute is often the right tool. A good next step is to compare this topic with other Swift attributes such as @available and @escaping so you can see how attributes help express API intent clearly.