Result Type — een container-type dat het resultaat vertegenwoordigt van een bewerking die kan eindigen in succes (Success) of fout (Failure). In tegenstelling tot uitzonderingen geeft Result de fout door als een gewone waarde zonder het afwikkelen van de stack, waardoor de afhandeling van verwachte fouten veiliger en beter samenstelbaar is. Volgens Apple Swift Documentation (2026), stelt Result<Success, Failure> in Swift je in staat om ketens van bewerkingen te combineren met automatische foutafhandeling via map en flatMap, zonder de uitvoering van het programma te onderbreken.
Belangrijkste punten
Result Type is een generiek type dat het resultaat inkapselt van de uitvoering van een bewerking die zowel succesvol als met een fout kan eindigen. In tegenstelling tot uitzonderingen, waar de fout de normale uitvoeringsstroom onderbreekt en het afwikkelen van de stack vereist om een catch te vinden, geeft Result de fout door als een gewone waarde — de aanroepende partij ontvangt altijd een object en beslist zelf hoe ermee om te gaan. Dit is vooral nuttig voor verwachte fouten: ongeldige invoer, bedrijfsregels, serverweigering, waar een uitzondering een te zwaar mechanisme zou zijn.
Het concept van Result komt uit functioneel programmeren, waar vergelijkbare typen Either (of Left/Right) worden genoemd. In Swift werd Result een standaardtype in Swift 5.0, in Kotlin verscheen Result<T> in de standaardbibliotheek, in Dart 3.0 — ingebouwd Result<T>. Elke implementatie biedt methoden om met de container te werken: map (transformatie van de succeswaarde), flatMap (keten van Result-functies), mapError (transformatie van de fout), fold (afhandeling van beide gevallen). Result Type — de hoeksteen van functionele foutafhandeling in mobiele ontwikkeling, een alternatief voor try-catch voor verwachte scenario's.
Het belangrijkste voordeel van Result is de samenstelbaarheid. Je kunt meerdere bewerkingen, die elk een fout kunnen retourneren, combineren in één keten zonder geneste try-catch. Als een bewerking in de keten eindigt met Failure, wordt de hele keten onderbroken en wordt Failure geretourneerd — zonder één if-voorwaarde of catch-blok. Dit maakt de code lineair en leesbaar, vooral in scenario's met meerdere opeenvolgende API-verzoeken of controles van bedrijfsregels.
In Swift is Result<Success, Failure> een enum met twee gevallen: .success(Success) en .failure(Failure), waarbij Failure is beperkt door het Error-protocol. Swift Result is een volledig functioneel type met de methoden map, flatMap, mapError en get. get is een bijzondere methode: het retourneert de Success-waarde als het resultaat succesvol is en gooit een fout als het Failure is. Dit maakt het mogelijk om Result te gebruiken als een brug tussen functionele en op uitzonderingen gebaseerde stijlen: behandel via map/flatMap in een keten, en aan het einde — get via do-catch voor integratie met throws-code.
Swift Result is een enum met generieke parameters, waardoor de compiler exhaustieve afhandeling kan controleren via switch of do-catch. Als je een nieuw geval toevoegt aan de enum NetworkError, geeft de compiler een fout in alle switch-expressies waar dit geval niet wordt afgehandeld. Exhaustieve controle (exhaustive checking) — het belangrijkste voordeel van Result boven uitzonderingen: de compiler garandeert dat alle mogelijke fouten in de compilatiefase in aanmerking worden genomen. In tegenstelling hiermee worden uitzonderingen in Swift niet gecontroleerd door de compiler (alleen throws wordt gedeclareerd, maar niet het fouttype).
enum NetworkError: Error {
case badURL
case requestFailed(String)
case decodingFailed
}
func fetchUser(id: Int) -> Result<User, NetworkError> {
guard let url = URL(string: "https://api.example.com/users/\(id)") else {
return .failure(.badURL)
}
let result = performRequest(url: url)
switch result {
case let .success(data):
if let user = try? JSONDecoder().decode(User.self, from: data) {
return .success(user)
}
return .failure(.decodingFailed)
case let .failure(error):
return .failure(.requestFailed(error.localizedDescription))
}
}
// Gebruik met switch
let result = fetchUser(id: 42)
switch result {
case .success(let user):
showUser(user)
case .failure(.badURL):
logError("Invalid URL")
case .failure(.requestFailed(let msg)):
showAlert(msg)
case .failure(.decodingFailed):
logError("Decoding error")
}
Result<Success, Failure> maakt het mogelijk om de fout op typeniveau te typeren: de functie fetchUser retourneert Result<User, NetworkError>, waarbij NetworkError een concrete enum is met drie gevallen. De aanroepende partij behandelt elk geval via een switch met exhaustieve dekking — de compiler controleert of alle varianten zijn afgehandeld. Exhaustieve switch — het belangrijkste voordeel van Result boven uitzonderingen: de compiler garandeert dat je niet vergeet .badURL, .requestFailed of .decodingFailed af te handelen. In het geval van uitzonderingen vereist de compiler geen afhandeling en een onopgevangen catch(.badURL) kan gemakkelijk worden gemist in een review.
In Kotlin is Result<T> een ingebouwd type uit de standaardbibliotheek, dat succes (T) of fout (Throwable) vertegenwoordigt. In tegenstelling tot Swift staat Result in Kotlin niet toe om een specifiek fouttype aan te geven — alleen Throwable. Dit is gedaan voor eenvoud, maar vereist extra controle van het fouttype via een when-expressie. Kotlin Result ondersteunt de functies fold (afhandeling van beide gevallen), getOrNull (succes of null), getOrDefault (succes of standaardwaarde), map, recover, andThen. Een bijzonderheid van Kotlin: Result is niet bedoeld voor directe propagatie over functiegrenzen heen — het kan niet worden gebruikt als retourtype voor methoden van de Android SDK of Kotlin Coroutines API zonder extra aanpassing.
fun parseJson(input: String): Result<JsonObject> {
return runCatching {
JsonParser.parseString(input).asJsonObject()
}
}
fun validateEmail(email: String): Result<String> {
return if (email.contains("@")) {
Result.success(email.trim())
} else {
Result.failure(IllegalArgumentException("Invalid email"))
}
}
data class SignupData(val name: String, val email: String)
fun processSignup(name: String, email: String): SignupResult {
return validateEmail(email).fold(
onSuccess = { validateName(name) },
onFailure = { SignupResult.Error("Invalid email") }
)
}
runCatching — een handige wrapper in Kotlin die elke uitzondering opvangt en Result.failure retourneert. validateEmail retourneert Result.success of Result.failure afhankelijk van de controle. fold handelt beide gevallen compact af: bij Success wordt de volgende validatiefunctie aangeroepen, bij Failure — wordt SignupResult.Error geretourneerd. Belangrijk: Kotlin Result is niet bedoeld voor opslag in data class-velden of directe overdracht over de grenzen van suspend-functies — gebruik je eigen sealed class (Success/Error/Loading) voor het weergeven van UI-toestanden in Jetpack Compose of MVVM.
Sinds Dart 3.0 is in de standaardbibliotheek een ingebouwd Result<T> verschenen — een sealed class met twee constructors: T.ok() (succes) en Error.error() (fout met Object en StackTrace). Vóór Dart 3.0 gebruikten Flutter-ontwikkelaars Either<L, R> uit de dartz-package of aangepaste sealed classes. Het ingebouwde Result in Dart is minimalistisch: het biedt niet direct map/flatMap aan — deze functies moeten worden geïmplementeerd via when of met behulp van extension methods. Voor serieuze functionele afhandeling blijft Either uit fpdart een krachtigere oplossing met ondersteuning voor map, flatMap, mapLeft, fold, andThen en bind-operatoren.
Either<L, R> — een links-geassocieerd type uit de fpdart-package, waarbij Left — fout, Right — succes. In tegenstelling tot het ingebouwde Result<T>, typeert Either de fout op het niveau van de typeparameter (L), waardoor fouttypen in de compilatiefase kunnen worden onderscheiden. De fpdart-package biedt een volledige set functionele combinators: map (Right -> Right), mapLeft (Left -> Left), flatMap (bind — geneste Either), andThen (keten zonder transformatie), fold (uitgang uit Either), getOrElse (standaardwaarde). Voor Flutter-applicaties met een functionele benadering is Either de facto de standaard.
import 'dart:convert';
import 'package:fpdart/fpdart.dart';
class UserService {
Either<AppError, User> fetchUser(String id) {
try {
final response = await http.get(
Uri.parse('https://api.example.com/users/$id')
);
if (response.statusCode == 200) {
final user = User.fromJson(
json.decode(response.body)
);
return Either.of(user);
}
return Either.left(
AppError.serverError(response.statusCode)
);
} on SocketException catch (e) {
return Either.left(AppError.networkError(e.message));
}
}
}
// Gebruik met fold
final result = await service.fetchUser('42');
result.fold(
(left) => showError(left.message),
(right) => showUser(right),
);
Either<L, R> uit fpdart — links-geassocieerd type: Left — fout, Right — succes. fetchUser retourneert Either<AppError, User>, waarbij AppError een sealed class is met specifieke fouttypen (serverError, networkError). fold handelt beide gevallen af: de eerste callback voor Left (fout), de tweede voor Right (succes). In Dart 3.0 ondersteunt het ingebouwde Result ook fold, maar biedt het geen map/flatMap. Voor ketens biedt Either uit fpdart map, flatMap (bind), mapLeft, andThen — een volledige set functionele combinators voor foutcompositie.
Het belangrijkste voordeel van Result boven uitzonderingen is compositie. Als je meerdere bewerkingen hebt, die elk een fout kunnen retourneren, kun je ze combineren in een keten via map en flatMap zonder een enkele geneste if of try-catch. map transformeert de succeswaarde: Result.success(x) -> Result.success(f(x)). flatMap (ook wel bind of andThen genoemd) — voor gevallen waarin de transformatie zelf Result retourneert: Result.success(x) -> f(x) -> Result<Y>. Als bij een stap Failure optreedt, worden volgende bewerkingen niet uitgevoerd — de keten wordt onderbroken.
data class UserRequest(val userId: String, val token: String)
sealed class AuthError {
data object InvalidToken : AuthError()
data class UserNotFound(val id: String) : AuthError()
}
typealias Outcome<T> = Either<AuthError, T>
fun validateToken(token: String): Outcome<String> =
if (token.isNotBlank()) Either.right(token)
else Either.left(AuthError.InvalidToken)
fun fetchProfile(userId: String): Outcome<Profile> =
if (userId == "42") Either.right(Profile("Alice"))
else Either.left(AuthError.UserNotFound(userId))
// Compositie via flatMap (andThen in fpdart)
val result = validateToken("abc123")
.flatMap { fetchProfile("42") }
.map { it.name }
.getOrElse { "Guest" }
println(result) // "Alice"
Keten: validateToken -> fetchProfile -> map name -> getOrElse „Guest“. Als validateToken Left (InvalidToken) retourneert, wordt de keten onderbroken en wordt „Guest“ geretourneerd. Als fetchProfile Left (UserNotFound) retourneert — ook „Guest“. Als beide bewerkingen succesvol zijn — de profielnaam. flatMap maakt het mogelijk om Either-functies, die elk kunnen falen, te combineren in één lineaire keten. In de traditionele uitzonderingsstijl zou dezelfde code twee geneste try-catch of null-controles vereisen. getOrElse aan het einde — het uitgangspunt van de compositie, dat een standaardwaarde biedt voor het Failure-geval.
Result en uitzonderingen — zijn geen elkaar uitsluitende benaderingen. Elk heeft zijn toepassingsgebied en in een goed ontworpen mobiele applicatie worden beide gebruikt. De keuze hangt af van of de fout verwacht (expected) of onverwacht (unexpected) is. Result — voor verwachte fouten die deel uitmaken van de bedrijfslogica: ongeldige e-mail, onvoldoende saldo op de rekening, overschrijding van de aanvraaglimiet. Uitzonderingen — voor onverwachte systeemfouten: netwerkverlies, OutOfMemoryError, NullPointerException (die er niet zouden moeten zijn, maar ze gebeuren).
| Criterium | Result Type | Uitzonderingen (Exception/Error) |
|---|---|---|
| Fouttype | Verwacht (bedrijfslogica) | Onverwacht (systeem) |
| Prestatie | Lage kosten (zonder stack-afwikkeling) | Hoge kosten (stack unwinding, StackTrace vastleggen) |
| Compositie | Via map/flatMap — lineaire ketens | Geneste try-catch — moeilijk te lezen |
| Compiler | Exhaustieve controle (switch/when) | Alleen checked exceptions in Java |
| Uitvoeringsstroom | Wordt niet onderbroken — fout als waarde | Wordt onderbroken tot de dichtstbijzijnde catch |
| Testen | Makkelijk: resultaat controleren, assert isSuccess/isError | Vereist assertThrows en mock-objecten |
| Wanneer gebruiken | Bedrijfsvalidatie, aanvraagketens, formulieren | Netwerkverlies, I/O-fouten, systeemcrashes |
Praktische regel: als de fout deel uitmaakt van de normale werkstroom van de applicatie (gebruiker heeft een ongeldige e-mail ingevoerd, onvoldoende toegangsrechten) — gebruik Result. Als de fout een uitzonderlijke situatie is (server reageert niet, geheugen op) — gebruik uitzonderingen. In mobiele ontwikkeling is Result op de grenzen van lagen (UseCase -> ViewModel) en uitzonderingen binnen lagen (API -> Repository) — een veelvoorkomend patroon dat de voordelen van beide benaderingen combineert.
Migratie van bestaande op uitzonderingen gebaseerde code naar Result moet geleidelijk zijn. Begin met de grenzen van lagen: wikkel aanroepen van throws-functies in Result { try ... } (Swift) of runCatching { ... } (Kotlin). Vervang vervolgens het retourtype van Repository- en UseCase-methoden door Result/Either, terwijl je de interne implementatie op uitzonderingen houdt. In de laatste fase migreer je ViewModel: in plaats van UiState met uitzonderingen, gebruik je sealed class UiState<T> (Loading, Success, Error), waarbij Error de domeinfout opslaat, niet Throwable. Geleidelijke migratie maakt het mogelijk om elke laag afzonderlijk te testen zonder globale refactoring.
Veelgestelde vragen
Optional (T?) vertegenwoordigt de aanwezigheid of afwezigheid van een waarde — nil betekent “geen gegevens”, maar zegt niet waarom. Result (Success/Failure) bevat niet alleen het succes, maar ook de oorzaak van de fout met een concreet type. Gebruik Optional wanneer afwezigheid van waarde normaal is (bijv. een optioneel profielveld), Result — wanneer je informatie over de fout nodig hebt.
In Swift gebruik je Result { try throwingFunc() } — de constructor van Result accepteert een throws-closure. In Kotlin — runCatching { throwingFunc() }, die Result<T> retourneert. In Dart — Result<T>.tryCatch(() => throwingFunc()). Dit maakt het mogelijk om op uitzonderingen gebaseerde code eenvoudig te integreren in Result-ketens.
Gebruik mapError (Swift) of mapLeft (Either in Dart/Kotlin) voor transformatie van het fouttype zonder de succeswaarde te wijzigen. Als je beide gevallen moet afhandelen en een enkele waarde moet retourneren — gebruik fold. Voor logging zonder de keten te onderbreken, gebruik onFailure (Kotlin) of een prefix inspection point.
Ja, maar voorzichtig. Kotlin Result<T> wordt niet aanbevolen als direct retourtype van suspend-functies vanwege kenmerken van de K2-compiler en reflectie. Gebruik je eigen sealed class NetworkResult<T> (Success, Error, Loading) voor het weergeven van toestanden in coroutines. Voor verwachte fouten in bedrijfslogica is Either uit Arrow een krachtiger alternatief.
fold — een methode die twee callbacks accepteert: onSuccess (voor het succesgeval) en onFailure (voor het foutgeval), en retourneert een enkele waarde van elk type. Dit is het equivalent van een switch-expressie, maar in de vorm van een hogere-ordefunctie. fold — het belangrijkste uitgangspunt uit Result-ketens, waar je Success/Failure omzet naar UiState, een string voor de gebruiker of een andere Result.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook