Swift & Objective-C

Swift throws and Result: Preserve the Reason for Failure

Compare two errors collapsed by try? with typed throws and stored Result values, then recover successful values or rethrow failures using get.

2 min read
Cover image for Swift throws and Result: Preserve the Reason for Failure

If different failures require different actions, preserve them with throws or Result instead of erasing them with try?. throws propagates errors through a call; Result stores success or failure as a value for later handling.

Mechanism and design choice

ParseError distinguishes nonnumeric input from a negative number. throws(ParseError) specifies the thrown error type, allowing do-catch to select an appropriate response. try? collapses both failures into nil, which is insufficient when recovery policies differ. Primary source

Reproduce the experiment

Pass cat, -1, 0, and 42 through the same parser. Verify that try? yields nil for both failures, then store each outcome as Result<Int, ParseError>. A typed do block makes the caught error type explicit; get retrieves stored successes or rethrows failures.

This standalone example was run on 2026-10-23 with Apple Swift 6.4 targeting arm64 macOS, in Swift 6 language mode. It needs no network or additional packages.

Download the complete Swift file

Run the command in the download directory. Preconditions stop execution if a check fails. Do not use -Ounchecked: it removes these checks.

swift -swift-version 6 swift-error-handling-throws-result.swift
enum ParseError: Error, Equatable { case notInteger, negative }
func parse(_ raw: String) throws(ParseError) -> Int {
    guard let value = Int(raw) else { throw .notInteger }
    guard value >= 0 else { throw .negative }
    return value
}

precondition((try? parse("cat")) == nil && (try? parse("-1")) == nil)
let results: [Result<Int, ParseError>] = ["cat", "-1", "0", "42"].map { raw in
    do throws(ParseError) { return .success(try parse(raw)) }
    catch { return .failure(error) }
}
precondition(results == [.failure(.notInteger), .failure(.negative), .success(0), .success(42)])
do {
    _ = try results[1].get()
    preconditionFailure("failure was lost")
} catch ParseError.negative {}
let recovered = try results[3].get()
precondition(recovered == 42)
print("try?-failures=nil,nil result=notInteger,negative,0,42 get-error=negative")

Recorded output and interpretation

try?-failures=nil,nil result=notInteger,negative,0,42 get-error=negative

The array retains notInteger, negative, 0, and 42 separately. Calling get on the failed result throws negative again; the successful result returns 42. No network or file I/O is involved in this check of error-information preservation.

Scope and limitations

throws cannot catch every program failure. Traps from force unwrapping or failed preconditions are outside this contract. A failing try! is not a normal recovery path either. This experiment only runs synchronous parsing, not asynchronous cancellation, retries, or file failures.

What to check next

Define error cases around actions the caller can distinguish. Use Optional for simple absence, throws to convey reasons, and Result to store or aggregate outcomes. Attach UI messaging and logging at a boundary that preserves the actual cause.

Related experiment

Sources and verification

  • The Swift Programming Language: Error HandlingSwift.org · Official documentation · Checked October 23, 2026Supports: Source for the implementation contract and explanation. The output was checked separately using the experiment included here.