Swift 6.2 Typed Throws in 2026: Complete Gids voor Type-Veilige Foutafhandeling
Typed throws in Swift 6.2 laten je het exacte fouttype in de functiesignatuur vastleggen. Deze gids behandelt syntaxis, generieke code, async-interactie, prestaties op embedded, en de valkuilen bij publieke API's.
Typed throws in Swift 6.2 zijn een taalfunctie waarmee je in de functiesignatuur exact het fouttype declareert dat een functie kan gooien (bijvoorbeeld func parse() throws(ParseError) -> Token), zodat de compiler de foutafhandeling statisch controleert in plaats van je te dwingen elke catch-tak op any Error te matchen. Dat sluit een gat dat sinds de Objective-C-dagen bestond, waarin NSError** nooit iets zei over welk domein je zou ontvangen. Deze gids behandelt de volledige syntaxis, generieke abstracties met rethrows, interactie met async, prestatiekarakteristieken en (het belangrijkste) wanneer je ze niet moet gebruiken.
Typed throws worden geschreven als throws(E) waarbij E een concreet fouttype is dat aan Error voldoet.
Een klassieke throws-declaratie is nu syntactische suiker voor throws(any Error); Result-achtige exhaustiviteit komt gratis mee.
Generieke code kan foutclosures propageren met throws(E) in combinatie met rethrows, wat de oude rethrows-regels formaliseert.
Typed throws elimineren de existentiële any Error-boxing en verminderen de code-grootte in hot paths op embedded systemen.
Voor publieke bibliotheek-API's is throws (untyped) meestal nog steeds de juiste keuze, want je verplaatst evolutie-lasten anders naar consumenten.
Interactie met async is soepel: async throws(E) werkt met Task, TaskGroup en AsyncSequence.
Syntaxis en basis: hoe schrijf je typed throws?
Oké, laten we beginnen bij het begin. De basissyntaxis van typed throws in Swift 6.2 is één regel: plaats het fouttype tussen haakjes direct na het sleutelwoord throws. Het type moet aan het Error-protocol voldoen, net als bij elke andere throwing-functie. Wat verandert, is dat de compiler nu een gesloten lijst van mogelijke fouten kent, en dat je catch-blokken exhaustief moet zijn zoals een switch op een enum.
enum ParseError: Error {
case unexpectedToken(line: Int)
case unterminatedString
case invalidNumber(String)
}
func parseToken(from input: String) throws(ParseError) -> Token {
guard let first = input.first else {
throw ParseError.unexpectedToken(line: 0)
}
// ...
return Token.identifier(String(first))
}
do {
let token = try parseToken(from: source)
print(token)
} catch .unexpectedToken(let line) {
logger.error("Onverwacht token op regel \(line)")
} catch .unterminatedString {
logger.error("String niet afgesloten")
} catch .invalidNumber(let raw) {
logger.error("Ongeldig getal: \(raw)")
}
Merk drie dingen op. Ten eerste hoef je in de catch-takken de enum-naam niet te herhalen, want de compiler weet dat error van het type ParseError is. Ten tweede is er geen catch-all nodig zolang je alle cases behandelt; de checker gedraagt zich als switch. Ten derde blijft throw zelf ongewijzigd: alleen de signatuur draagt de type-informatie.
Een klassieke declaratie zoals func f() throws is in Swift 6.2 formeel syntactische suiker voor func f() throws(any Error). Dat is belangrijk om te begrijpen, want het betekent dat elke bestaande throwing-API automatisch samenwerkt met typed-throws-code zonder verandering. Je verliest alleen de statische garantie.
Typed throws vs untyped throws vs Result
Vóór typed throws had je drie manieren om herstelbare fouten te modelleren: throws, Result<Success, Failure>, en handmatige optionele returnwaarden. Elk had z'n compromis. Typed throws combineren de sterke punten van de eerste twee. De volgende tabel vat de dimensies samen die het vaakst terugkomen in echte API-beoordelingen.
Dimensie
throws (any Error)
throws(E), typed
Result<T, E>
Statische fouttypecontrole
Nee
Ja
Ja
Exhaustieve catch
Vereist catch-all
Zoals switch
N.v.t. (get()/switch)
API-evolutie (nieuwe fout toevoegen)
Bron-compatibel
Bron-brekend
Bron-brekend
Existentiële boxing van fout
Ja
Nee
Nee
Werkt met try? / try!
Ja
Ja
Nee (handmatig)
Interactie met async/await
Direct
Direct
Onhandig
Geschikt voor publieke SDK's
Vaak ja
Alleen bij stabiele foutdomeinen
Zelden
Merk op dat API-evolutie in het nadeel van typed throws werkt. Zodra je een nieuwe case aan het gedeclareerde fouttype toevoegt, breken consumenten die geen catch-all hebben. Precies daarom blijven Apple's eigen Foundation-API's grotendeels bij untyped throws. De Objective-C-heritage van NSError-domeinen leerde dat elk fouttype ooit uitbreidt.
Typed throws in generieke code en rethrows
Eerlijk gezegd is de meest interessante toepassing van typed throws niet de concrete gebruikssituatie hierboven, maar de generieke. Vóór Swift 6.2 gebruikten hogere-orde functies zoals map, filter en reduce een informele rethrows-regel: als de closure gooit, gooit ook de wrapper. De regel was correct maar ondoorzichtig, en werkte alleen voor any Error.
Met typed throws kun je de fout van een closure als generieke parameter vastleggen en doorgeven:
extension Sequence {
func mapTyped<T, E>(
_ transform: (Element) throws(E) -> T
) throws(E) -> [T] {
var result: [T] = []
result.reserveCapacity(underestimatedCount)
for element in self {
result.append(try transform(element))
}
return result
}
}
enum ValidationError: Error {
case tooShort
case containsDigits
}
let names = ["Anna", "Bram", "Chris"]
do {
let validated = try names.mapTyped { name throws(ValidationError) in
guard name.count >= 3 else { throw .tooShort }
return name.uppercased()
}
print(validated)
} catch .tooShort {
print("Naam te kort")
} catch .containsDigits {
print("Cijfers gevonden")
}
Als de closure niet gooit, wordt E geïnfereerd als Never, en throws(Never) is equivalent aan een niet-throwing functie. De compiler laat je de try zelfs weglaten. Dat is de reden dat je in echte code steeds vaker Never-parameterisatie ziet: het is de bron-compatibele manier om throwing- en niet-throwing-varianten te unificeren in één API.
De klassieke rethrows-clausule blijft geldig en werkt nog steeds voor any Error, maar wordt intern in Swift 6.2 gedefinieerd in termen van typed throws met een impliciete E-parameter. Dat maakt rethrows nu een uitleg in plaats van een uitzondering.
Werken typed throws met async, Task en TaskGroup?
Ja, typed throws combineren zonder speciale syntaxis met async. Dat is significant, want in eerdere Swift-versies was er discussie of async-throwing-signaturen een aparte behandeling zouden krijgen. In 6.2 volgen ze exact dezelfde regels: async throws(E) is een async-functie die alleen fouten van type E gooit.
enum NetworkError: Error {
case offline
case http(status: Int)
case decoding(DecodingError)
}
func fetchProfile(id: UUID) async throws(NetworkError) -> Profile {
guard NWPathMonitor.current.status == .satisfied else {
throw .offline
}
let (data, response) = try await URLSession.shared.data(from: url(for: id))
guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else {
throw .http(status: (response as? HTTPURLResponse)?.statusCode ?? -1)
}
do {
return try JSONDecoder().decode(Profile.self, from: data)
} catch let decodingError as DecodingError {
throw .decoding(decodingError)
} catch {
throw .http(status: -1) // val terug op onbekend
}
}
Bij TaskGroup heb je nog steeds de throwing-variant nodig (withThrowingTaskGroup), maar deze accepteert nu een generieke Failure-parameter waarmee je het fouttype van elk kind vastlegt. Onder de motorkap valt Failure terug op any Error voor bron-compatibiliteit. Voor de patronen waarop deze groepen bouwen, is onze gids over Foundation Models en on-device AI met Swift een goede opvolger. Die combineert concurrency, foutgevoelige model-calls en typed throws in een concrete pipeline.
Zijn typed throws sneller dan gewone throws?
In doorsnee-app-code is het prestatieverschil verwaarloosbaar. In embedded en performance-kritische contexten is het meetbaar. De reden is de existentiële representatie van any Error. Zonder typed throws draagt Swift een existential container van 40 bytes op 64-bit, met een indirecte allocatie voor grote payloads. Bij typed throws met een klein enum-fouttype past de fout gewoon in een register of stack-slot, zonder heap-allocatie en zonder witnesstabel-lookup bij het gooien.
Voor het Swift-team was dit de doorslaggevende motivatie op embedded platforms zoals watchOS en de Embedded Swift-modus. Zie de Embedded Swift-aankondigingen op swift.org voor concrete cijfers over code-grootte-reducties in de orde van 15 tot 30% op firmware-projecten. Op iOS-apps met moderne hardware is dat verschil onzichtbaar; op een sensormodule met 128 KB flash is het bepalend.
Wat je in profielen wél zult zien op standaard-app-code: minder swift_allocError-aanroepen in throw-hete lussen, en minder swift_getGenericMetadata-hits in catch-takken die naar concrete types casten. Dat is winst die je alleen ontgrendelt als je type-checker-tegemoetkomingen zoals catch-all-blokken vermijdt.
Wanneer moet je typed throws vermijden?
Dit is het meningsdeel, en ik heb daar in mijn jaren met Swift-teams sterke visies over gevormd. Typed throws zijn een uitstekend hulpmiddel op de juiste abstractielaag, en een risico op de verkeerde. Ik hit deze val zelf toen ik een SDK bouwde en achteraf een fouttype moest opsplitsen (dat kostte een major-versie). Drie categorieën waarin ik ze inmiddels vermijd:
Publieke bibliotheek-API's met evoluerende foutdomeinen. Als je bibliotheek in v1 een DatabaseError-enum met drie cases publiceert, en je in v2 een nieuwe soort I/O-fout tegenkomt, ben je bron-brekend. Untyped throws laat je een nieuw type onder any Error gooien zonder dat consumenten hercompileren.
Foutdoorgifte via lagen die je niet controleert. Zodra een fout door drie lagen frameworks gaat waarvan er twee any Error gebruiken, is de throws(E)-declaratie in de onderste laag pure ceremonie. Het typesignaal gaat verloren zodra het door het eerste onbekende throw-punt gaat.
Situaties waar catch-all logisch is. Netwerkverzoeken, gebruikersinvoerparsers en veel scriptcode gedragen zich pragmatisch beter met een enkelvoudige "toon-de-gebruiker-een-melding"-tak. Typed throws dwingen je dan tot ceremoniële exhaustiviteit die geen bug voorkomt.
De praktische heuristiek die ik hanteer: gebruik typed throws in module-interne code waar je zowel de gooier als de vanger bezit, en waar de foutverzameling stabiel is. Gebruik throws op modulegrenzen. Deze scheiding geeft je de compiler-hulp waar het telt, zonder een evolutielast op consumenten te leggen.
Migratie van bestaande foutafhandelingscode
Een migratie naar typed throws is niet all-or-nothing. Omdat throws equivalent is aan throws(any Error), kun je functie-voor-functie updaten zonder consumenten aan te raken. Een pragmatische volgorde die ik in m'n laatste project goed heb zien werken:
Begin bij bladeren. Kleine hulpfuncties die één enum-fouttype gooien zijn kandidaat voor throws(E). Dit ontgrendelt exhaustieve catch op de aanroepsite zonder de overige code te beïnvloeden.
Werk naar boven. Zodra meerdere aangrenzende functies hetzelfde fouttype gooien, kun je de gemeenschappelijke bovenlaag ook typen. Als de bovenlaag verschillende fouttypes verwerkt, voeg een adapter-enum toe die alle mogelijkheden verpakt.
Test met exhaustieve catch-blokken. Zodra typed throws in een aanroepsite van kracht is, laat de compiler geen ontbrekende cases meer toe. Dit is het moment om je unit-tests bij te werken. Onze gids over migratie naar het nieuwe Swift Testing-framework laat zien hoe je fouttypes in de nieuwe #expect(throws:)-macro's koppelt.
Behoud throws op publieke oppervlakken. Tenzij je een stabiel gedocumenteerd foutdomein hebt, is het bijna altijd fout om je publieke API-oppervlak naar getypeerde varianten te verplaatsen. Je kunt de winst intern behouden en het extern verpakken.
Voor de volledige taalregels raad ik aan om SE-0413 op swift-evolution te lezen. Het proposal blijft het definitieve document, en de "Alternatives Considered"-sectie legt uit waarom subtype-relaties tussen foutdomeinen bewust niet werden gemodelleerd.
Veelgestelde vragen
Wat is het verschil tussen throws(E) en throws in Swift 6.2?
throws is nu formeel syntactische suiker voor throws(any Error). Met throws(E) geef je een concreet fouttype op, wat de compiler in staat stelt exhaustieve catch-controle te doen en de existentiële boxing van any Error te vermijden.
Kun je typed throws combineren met async en await?
Ja. Schrijf simpelweg async throws(E). Alle bestaande concurrency-constructies zoals Task, withThrowingTaskGroup en AsyncSequence accepteren getypeerde foutparameters die terugvallen op any Error voor bron-compatibiliteit.
Zijn typed throws sneller dan gewone throws?
Op moderne iOS-hardware is het verschil onzichtbaar in doorsnee-app-code. Op embedded targets en in hot-path-lussen elimineren ze existentiële boxing en dynamische witnesstabel-lookups, wat 15 tot 30% code-grootte-reducties oplevert op firmware-projecten volgens Swift-team benchmarks.
Moet ik mijn publieke API migreren naar typed throws?
Meestal niet. Elke nieuwe foutcase in het getypeerde enum is bron-brekend voor consumenten. Behoud throws op publieke oppervlakken en gebruik throws(E) alleen intern of in echt stabiele foutdomeinen.
Wat betekent throws(Never) in Swift 6.2?
Een functie met throws(Never) kan bewijsbaar geen fout gooien; de compiler behandelt het als een niet-throwing functie. Dit is nuttig in generieke code waar dezelfde signatuur throwing- en niet-throwing-varianten moet unificeren zonder duplicatie.
Kan Result nog nuttig zijn met typed throws beschikbaar?
Ja, voor het opslaan van uitkomsten in eigenschappen, doorgeven aan Objective-C-callbacks, en het samenstellen van reactieve pipelines. Typed throws zijn geoptimaliseerd voor synchrone controlefluw; Result blijft de juiste keuze voor waarden die als data reizen.
Migreer je Swift-tests van XCTest naar Swift Testing in Xcode 26. Praktische voorbeelden van @Test, #expect, parameterized tests en parallelle uitvoering.
SwiftData in iOS 26 ondersteunt eindelijk echte class inheritance. Leer hoe je model-hiërarchieën opzet, queries optimaliseert met #Index en #Unique, en schema-migraties uitvoert — met werkende codevoorbeelden.
Leer hoe je Liquid Glass implementeert in je SwiftUI-apps voor iOS 26. Van de .glassEffect()-modifier en GlassEffectContainer tot morph-transities, toolbars, tab bars, sheets en best practices — inclusief werkende codevoorbeelden.