Result Type: ce este, tip-container Result și cum funcționează în dezvoltarea mobilă

Autor: IT Sectr Publicat: 2026-05-26 Timp de citire: 9 min

Result Type — un tip-container care reprezintă rezultatul unei operații care se poate încheia cu succes (Success) sau eroare (Failure). Spre deosebire de excepții, Result transmite eroarea ca o valoare obișnuită, fără derularea stivei, ceea ce face gestionarea erorilor așteptate mai sigură și mai compozabilă. Conform Apple Swift Documentation (2026), Result<Success, Failure> în Swift permite combinarea lanțurilor de operații cu gestionarea automată a erorilor prin map și flatMap, fără a întrerupe execuția programului.

Principalele puncte

  • Result Type — container generic cu două stări: Success (date) și Failure (eroare), fără derularea stivei
  • În Swift Result<Success, Failure> — tip încorporat cu metodele map, flatMap, mapError și get
  • În Kotlin Result<T> reprezintă succesul sau Throwable, cu funcțiile getOrNull, getOrDefault, fold
  • În Dart Result<T> standard disponibil din Dart 3.0, de asemenea Either din pachetul fpdart
  • Compoziția prin map și flatMap permite combinarea mai multor operații care returnează Result fără verificări imbricate

Ce este Result Type?

Result Type este un tip generic care încapsulează rezultatul executării unei operații care se poate încheia atât cu succes, cât și cu eroare. Spre deosebire de excepții, unde eroarea întrerupe fluxul normal de execuție și necesită derularea stivei pentru a găsi catch, Result transmite eroarea ca o valoare obișnuită — partea apelantă primește întotdeauna un obiect și decide singură cum să îl gestioneze. Acest lucru este util în special pentru erorile așteptate: intrare incorectă, reguli de afaceri, refuzul serverului, unde o excepție ar fi un mecanism prea greoi.

Conceptul Result provine din programarea funcțională, unde tipuri similare se numesc Either (sau Left/Right). În Swift, Result a devenit un tip standard în Swift 5.0, în Kotlin Result<T> a apărut în biblioteca standard, în Dart 3.0 — Result<T> încorporat. Fiecare implementare oferă metode pentru lucrul cu containerul: map (transformarea valorii de succes), flatMap (lanț de funcții Result), mapError (transformarea erorii), fold (gestionarea ambelor cazuri). Result Type — piatra de temelie a gestionării funcționale a erorilor în dezvoltarea mobilă, o alternativă la try-catch pentru scenarii așteptate.

Avantajul cheie al Result este compozabilitatea. Puteți combina mai multe operații, fiecare putând returna o eroare, într-un singur lanț fără try-catch-uri imbricate. Dacă orice operație din lanț se încheie cu Failure, întregul lanț este întrerupt și returnează Failure — fără nicio condiție if sau bloc catch. Acest lucru face codul liniar și lizibil, în special în scenarii cu mai multe interogări succesive către API sau verificări ale regulilor de afaceri.

Result în Swift

În Swift Result<Success, Failure> este un enum cu două cazuri: .success(Success) și .failure(Failure), unde Failure este limitat de protocolul Error. Swift Result este un tip funcțional complet cu metodele map, flatMap, mapError și get. get este o metodă specială: returnează valoarea Success dacă rezultatul este reușit și aruncă o eroare dacă este Failure. Acest lucru permite utilizarea Result ca punte între stilurile funcțional și bazat pe excepții: gestionați prin map/flatMap în lanț, iar la final — get prin do-catch pentru integrarea cu codul throws.

Enum Result și switch exhaustiv

Swift Result este un enum cu parametri generici, ceea ce permite compilatorului să verifice gestionarea exhaustivă prin switch sau do-catch. Dacă adăugați un nou caz la enum NetworkError, compilatorul va da o eroare în toate expresiile switch în care acest caz nu este gestionat. Verificarea exhaustivă (exhaustive checking) — principalul avantaj al Result față de excepții: compilatorul garantează că toate erorile posibile sunt luate în considerare în etapa de compilare. Spre deosebire de aceasta, excepțiile nu sunt verificate de compilator în Swift (doar throws este declarat, dar nu și tipul erorii).

swift
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))
    }
}

// Utilizare cu 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> permite tipificarea erorii la nivel de tip: funcția fetchUser returnează Result<User, NetworkError>, unde NetworkError este un enum concret cu trei cazuri. Partea apelantă gestionează fiecare caz prin switch cu acoperire exhaustivă — compilatorul verifică dacă toate variantele sunt gestionate. Switch exhaustiv — avantajul cheie al Result față de excepții: compilatorul garantează că nu veți uita să gestionați .badURL, .requestFailed sau .decodingFailed. În cazul excepțiilor, compilatorul nu necesită gestionare, iar un catch(.badURL) neprins poate fi ușor ratat în review.

Result în Kotlin

În Kotlin Result<T> este un tip încorporat din biblioteca standard, reprezentând succesul (T) sau eroarea (Throwable). Spre deosebire de Swift, în Kotlin Result nu permite specificarea unui tip concret de eroare — doar Throwable. Acest lucru a fost făcut pentru simplitate, dar necesită o verificare suplimentară a tipului de eroare prin expresia when. Kotlin Result suportă funcțiile fold (gestionarea ambelor cazuri), getOrNull (succes sau null), getOrDefault (succes sau valoare implicită), map, recover, andThen. Particularitatea Kotlin: Result nu este destinat propagării directe prin granițele funcțiilor — nu poate fi utilizat ca tip de returnare pentru metodele Android SDK sau Kotlin Coroutines API fără adaptare suplimentară.

kotlin
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 — un înveliș convenabil în Kotlin care prinde orice excepție și returnează Result.failure. validateEmail returnează Result.success sau Result.failure în funcție de verificare. fold gestionează ambele cazuri compact: la Success se apelează următoarea funcție de validare, la Failure — se returnează SignupResult.Error. Important: Kotlin Result nu este destinat stocării în câmpurile data class sau transmiterii directe prin granițele funcțiilor suspend — utilizați propriile sealed class (Success/Error/Loading) pentru reprezentarea stărilor UI în Jetpack Compose sau MVVM.

Result în Dart și Flutter

Din Dart 3.0 în biblioteca standard a apărut Result<T> încorporat — o sealed class cu doi constructori: T.ok() (succes) și Error.error() (eroare cu Object și StackTrace). Înainte de Dart 3.0, dezvoltatorii Flutter foloseau Either<L, R> din pachetul dartz sau sealed class-uri personalizate. Result încorporat în Dart este minimalist: nu oferă map/flatMap direct — aceste funcții trebuie implementate prin when sau utilizând extension methods. Pentru o gestionare funcțională serioasă, Either din fpdart rămâne o soluție mai puternică, cu suport pentru map, flatMap, mapLeft, fold, andThen și operatori bind.

Either din fpdart

Either<L, R> — un tip asociat la stânga din pachetul fpdart, unde Left — eroare, Right — succes. Spre deosebire de Result<T> încorporat, Either tipifică eroarea la nivelul parametrului de tip (L), permițând diferențierea tipurilor de erori în etapa de compilare. Pachetul fpdart oferă un set complet de combinatori funcționali: map (Right -> Right), mapLeft (Left -> Left), flatMap (bind — Either-uri imbricate), andThen (lanț fără transformare), fold (ieșire din Either), getOrElse (valoare implicită). Pentru aplicațiile Flutter cu abordare funcțională, Either este standardul de facto.

dart
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));
        }
    }
}

// Utilizare cu fold
final result = await service.fetchUser('42');
result.fold(
    (left)  => showError(left.message),
    (right) => showUser(right),
);

Either<L, R> din fpdart — tip asociat la stânga: Left — eroare, Right — succes. fetchUser returnează Either<AppError, User>, unde AppError — sealed class cu tipuri specifice de erori (serverError, networkError). fold gestionează ambele cazuri: primul callback pentru Left (eroare), al doilea pentru Right (succes). În Dart 3.0 Result încorporat suportă și el fold, dar nu oferă map/flatMap. Pentru lanțuri, Either din fpdart oferă map, flatMap (bind), mapLeft, andThen — un set complet de combinatori funcționali pentru compoziția erorilor.

Compoziția Result prin map și flatMap

Principalul avantaj al Result față de excepții este compoziția. Dacă aveți mai multe operații, fiecare putând returna o eroare, le puteți combina într-un lanț prin map și flatMap fără niciun if imbricat sau try-catch. map transformă valoarea de succes: Result.success(x) -> Result.success(f(x)). flatMap (numit și bind sau andThen) — pentru cazurile în care transformarea însăși returnează Result: Result.success(x) -> f(x) -> Result<Y>. Dacă la orice pas apare Failure, operațiile ulterioare nu se execută — lanțul este întrerupt.

kotlin
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))

// Compoziție prin flatMap (andThen în fpdart)
val result = validateToken("abc123")
    .flatMap { fetchProfile("42") }
    .map { it.name }
    .getOrElse { "Guest" }

println(result) // "Alice"

Lanțul: validateToken -> fetchProfile -> map name -> getOrElse „Guest". Dacă validateToken returnează Left (InvalidToken), lanțul este întrerupt și se returnează „Guest". Dacă fetchProfile returnează Left (UserNotFound) — de asemenea „Guest". Dacă ambele operații au succes — numele profilului. flatMap permite combinarea funcțiilor Either, fiecare putând eșua, într-un singur lanț liniar. În stilul tradițional al excepțiilor, același cod ar necesita două try-catch-uri imbricate sau verificări de null. getOrElse la sfârșit — punctul de ieșire din compoziție, care oferă o valoare implicită pentru cazul Failure.

Result vs Excepții: când să alegeți ce

Result și excepțiile — nu sunt abordări care se exclud reciproc. Fiecare are domeniul său de aplicare, iar într-o aplicație mobilă bine proiectată se folosesc ambele. Alegerea depinde de faptul dacă eroarea este așteptată (expected) sau neașteptată (unexpected). Result — pentru erorile așteptate care fac parte din logica de afaceri: email incorect, fonduri insuficiente în cont, depășirea limitei de cereri. Excepțiile — pentru erorile sistemice neașteptate: pierderea rețelei, OutOfMemoryError, NullPointerException (care nu ar trebui să apară, dar se întâmplă).

CriteriuResult TypeExcepții (Exception/Error)
Tip eroareAșteptate (logica de afaceri)Neașteptate (sistemice)
PerformanțăCost redus (fără derularea stivei)Cost ridicat (stack unwinding, capturare StackTrace)
CompozițiePrin map/flatMap — lanțuri liniareTry-catch-uri imbricate — greu de citit
CompilatorVerificare exhaustivă (switch/when)Doar checked exceptions în Java
Flux de execuțieNu este întrerupt — eroarea ca valoareEste întrerupt până la cel mai apropiat catch
TestareUșoară: verificarea rezultatului, assert isSuccess/isErrorNecesită assertThrows și obiecte mock
Când să utilizațiValidare de afaceri, lanțuri de cereri, formularePierderea rețelei, erori I/O, căderi de sistem

Regula practică: dacă eroarea face parte din fluxul normal de lucru al aplicației (utilizatorul a introdus un email incorect, nu are suficiente drepturi de acces) — folosiți Result. Dacă eroarea este o situație excepțională (serverul nu răspunde, memoria s-a terminat) — folosiți excepții. În dezvoltarea mobilă Result la granițele stratelor (UseCase -> ViewModel) și excepțiile în interiorul stratelor (API -> Repository) — un model comun care combină avantajele ambelor abordări.

Strategia de migrare de la excepții la Result

Migrarea codului existent bazat pe excepții la Result trebuie să fie treptată. Începeți cu granițele stratelor: înfășurați apelurile funcțiilor throws în Result { try ... } (Swift) sau runCatching { ... } (Kotlin). Apoi înlocuiți tipul de returnare al metodelor Repository și UseCase cu Result/Either, păstrând implementarea internă pe excepții. În ultima etapă, migrați ViewModel: în loc de UiState cu excepții, utilizați sealed class UiState<T> (Loading, Success, Error), unde Error stochează eroarea de domeniu, nu Throwable. Migrarea treptată permite testarea fiecărui strat separat, fără o refactorizare globală.

Întrebări frecvente

Cu ce se diferențiază Result Type de Optional/Option?

Optional (T?) reprezintă prezența sau absența unei valori — nil înseamnă „nu există date", dar nu spune de ce. Result (Success/Failure) conține nu doar succesul, ci și cauza erorii cu un tip concret. Utilizați Optional când absența valorii este normală (de exemplu, un câmp opțional de profil), Result — când aveți nevoie de informații despre eroare.

Cum se transformă o funcție throws în Result?

În Swift utilizați Result { try throwingFunc() } — constructorul Result acceptă o închidere throws. În Kotlin — runCatching { throwingFunc() }, care returnează Result<T>. În Dart — Result<T>.tryCatch(() => throwingFunc()). Acest lucru permite integrarea ușoară a codului bazat pe excepții în lanțurile Result.

Cum se gestionează o eroare în Result fără pierderea informațiilor?

Utilizați mapError (Swift) sau mapLeft (Either în Dart/Kotlin) pentru transformarea tipului de eroare fără a modifica valoarea de succes. Dacă trebuie să gestionați ambele cazuri și să returnați o singură valoare — utilizați fold. Pentru logare fără întreruperea lanțului, utilizați onFailure (Kotlin) sau un inspection point prefixat.

Se poate utiliza Result în Kotlin cu corutine?

Da, dar cu prudență. Kotlin Result<T> nu este recomandat ca tip de returnare direct al funcțiilor suspend din cauza particularităților compilatorului K2 și reflecției. Utilizați propria sealed class NetworkResult<T> (Success, Error, Loading) pentru reprezentarea stărilor în corutine. Pentru erorile așteptate în logica de afaceri, Either din Arrow este o alternativă mai puternică.

Ce este fold în contextul Result?

fold — o metodă care primește două callback-uri: onSuccess (pentru cazul de succes) și onFailure (pentru cazul de eroare), și returnează o singură valoare de orice tip. Este echivalentul unei expresii switch, dar sub forma unei funcții de ordin superior. fold — punctul principal de ieșire din lanțurile Result, unde transformați Success/Failure în UiState, șir pentru utilizator sau un alt Result.

Rezumat

  • Result Type — container generic (Success/Failure) pentru gestionarea sigură a erorilor așteptate fără derularea stivei
  • În Swift Result<Success, Failure> cu Failure: Error asigură verificare exhaustivă prin switch cu erori type-safe
  • În Kotlin Result<T> înfășoară succesul sau Throwable, runCatching — constructor convenabil din codul throws
  • În Dart Result<T> încorporat (Dart 3.0) și Either<L, R> din fpdart pentru compoziție avansată cu map/flatMap
  • Compoziția prin map (transformarea succesului) și flatMap (lanț de funcții Result) înlocuiește try-catch-urile imbricate
  • Result vs excepții: Result pentru erori de afaceri așteptate, excepții pentru defecțiuni sistemice neașteptate
  • Utilizați Result la granițele stratelor pentru gestionarea explicită și testabilă a erorilor fără întreruperea fluxului de execuție

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și