Result Type: co to jest, typ-kontener Result i jak działa w programowaniu mobilnym

Autor: IT Sectr Opublikowano: 2026-05-26 Czas czytania: 9 min

Result Type — typ-kontener, który reprezentuje wynik operacji, która może zakończyć się sukcesem (Success) lub błędem (Failure). W przeciwieństwie do wyjątków, Result przekazuje błąd jako zwykłą wartość bez rozwijania stosu, co sprawia, że obsługa oczekiwanych błędów jest bezpieczniejsza i łatwiejsza do komponowania. Według Apple Swift Documentation (2026), Result<Success, Failure> w Swift pozwala łączyć łańcuchy operacji z automatyczną obsługą błędów przez map i flatMap, bez przerywania wykonania programu.

Najważniejsze

  • Result Type — kontener generyczny z dwoma stanami: Success (dane) i Failure (błąd), bez rozwijania stosu
  • W Swift Result<Success, Failure> — wbudowany typ z metodami map, flatMap, mapError i get
  • W Kotlin Result<T> reprezentuje sukces lub Throwable, z funkcjami getOrNull, getOrDefault, fold
  • W Dart standardowy Result<T> dostępny od Dart 3.0, a także Either z pakietu fpdart
  • Kompozycja przez map i flatMap pozwala łączyć wiele operacji zwracających Result bez zagnieżdżonych sprawdzeń

Czym jest Result Type?

Result Type — to typ generyczny, który hermetyzuje wynik wykonania operacji, która może zakończyć się zarówno sukcesem, jak i błędem. W przeciwieństwie do wyjątków, gdzie błąd przerywa normalny przepływ wykonania i wymaga rozwijania stosu w celu znalezienia catch, Result przekazuje błąd jako zwykłą wartość — strona wywołująca zawsze otrzymuje obiekt i sama decyduje, jak go obsłużyć. Jest to szczególnie przydatne w przypadku oczekiwanych błędów: nieprawidłowe dane wejściowe, reguły biznesowe, odmowa serwera, gdzie wyjątek byłby zbyt ciężkim mechanizmem.

Koncepcja Result wywodzi się z programowania funkcyjnego, gdzie podobne typy nazywane są Either (lub Left/Right). W Swift Result stał się standardowym typem w Swift 5.0, w Kotlin Result<T> pojawił się w standardowej bibliotece, w Dart 3.0 — wbudowany Result<T>. Każda implementacja udostępnia metody do pracy z kontenerem: map (transformacja wartości sukcesu), flatMap (łańcuch funkcji Result), mapError (transformacja błędu), fold (obsługa obu przypadków). Result Type — kamień węgielny funkcyjnej obsługi błędów w programowaniu mobilnym, alternatywa dla try-catch dla oczekiwanych scenariuszy.

Kluczową zaletą Result jest komponowalność. Możesz łączyć wiele operacji, z których każda może zwrócić błąd, w jeden łańcuch bez zagnieżdżonych try-catch. Jeśli dowolna operacja w łańcuchu zakończy się Failure, cały łańcuch jest przerywany i zwraca Failure — bez ani jednego warunku if ani bloku catch. To sprawia, że kod jest liniowy i czytelny, szczególnie w scenariuszach z wieloma sekwencyjnymi zapytaniami do API lub sprawdzeniami reguł biznesowych.

Result w Swift

W Swift Result<Success, Failure> — to enum z dwoma przypadkami: .success(Success) i .failure(Failure), gdzie Failure jest ograniczony protokołem Error. Swift Result — pełnoprawny typ funkcyjny z metodami map, flatMap, mapError i get. get — szczególna metoda: zwraca wartość Success, jeśli wynik jest pomyślny, i rzuca błąd, jeśli Failure. Pozwala to używać Result jako mostu między stylem funkcyjnym a opartym na wyjątkach: obsługuj przez map/flatMap w łańcuchu, a na końcu — get przez do-catch w celu integracji z kodem throws.

Enum Result i wyczerpujący switch

Swift Result to enum z parametrami generycznymi, co pozwala kompilatorowi sprawdzać wyczerpującą obsługę przez switch lub do-catch. Jeśli do enum NetworkError dodasz nowy przypadek, kompilator zgłosi błąd we wszystkich wyrażeniach switch, w których ten przypadek nie jest obsłużony. Wyczerpujące sprawdzanie (exhaustive checking) — główna zaleta Result nad wyjątkami: kompilator gwarantuje, że wszystkie możliwe błędy są uwzględnione na etapie kompilacji. W przeciwieństwie do tego, wyjątki nie są sprawdzane przez kompilator w Swift (tylko throws jest deklarowane, ale nie typ błędu).

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

// Użycie z 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> pozwala typować błąd na poziomie typu: funkcja fetchUser zwraca Result<User, NetworkError>, gdzie NetworkError — konkretny enum z trzema przypadkami. Strona wywołująca obsługuje każdy przypadek przez switch z wyczerpującym pokryciem — kompilator sprawdza, czy wszystkie warianty są obsłużone. Wyczerpujący switch — kluczowa zaleta Result przed wyjątkami: kompilator gwarantuje, że nie zapomnisz obsłużyć .badURL, .requestFailed ani .decodingFailed. W przypadku wyjątków kompilator nie wymaga obsługi, a nieprzechwycony catch(.badURL) łatwo przeoczyć w review.

Result w Kotlin

W Kotlin Result<T> — wbudowany typ ze standardowej biblioteki, reprezentujący sukces (T) lub błąd (Throwable). W przeciwieństwie do Swift, w Kotlin Result nie pozwala określić konkretnego typu błędu — tylko Throwable. Zrobiono to dla uproszczenia, ale wymaga dodatkowego sprawdzenia typu błędu przez wyrażenie when. Kotlin Result obsługuje funkcje fold (obsługa obu przypadków), getOrNull (sukces lub null), getOrDefault (sukces lub wartość domyślna), map, recover, andThen. Cechą szczególną Kotlin: Result nie jest przeznaczony do bezpośredniego propagowania przez granice funkcji — nie można go używać jako typu zwracanego dla metod Android SDK lub Kotlin Coroutines API bez dodatkowej adaptacji.

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 — wygodna otoczka w Kotlin, która przechwytuje dowolny wyjątek i zwraca Result.failure. validateEmail zwraca Result.success lub Result.failure w zależności od sprawdzenia. fold obsługuje oba przypadki zwięźle: przy Success wywoływana jest następna funkcja walidacji, przy Failure — zwracany jest SignupResult.Error. Ważne: Kotlin Result nie jest przeznaczony do przechowywania w polach data class ani do przekazywania przez granice suspend-funkcji bezpośrednio — używaj własnych sealed class (Success/Error/Loading) do reprezentowania stanów UI w Jetpack Compose lub MVVM.

Result w Dart i Flutter

Od Dart 3.0 w standardowej bibliotece pojawił się wbudowany Result<T> — sealed class z dwoma konstruktorami: T.ok() (sukces) i Error.error() (błąd z Object i StackTrace). Przed Dart 3.0 programiści Flutter używali Either<L, R> z pakietu dartz lub własnych sealed class. Wbudowany Result w Dart jest minimalistyczny: nie udostępnia map/flatMap bezpośrednio — te funkcje trzeba zaimplementować przez when lub użyć extension methods. Do poważnej funkcyjnej obsługi Either z fpdart pozostaje potężniejszym rozwiązaniem z obsługą map, flatMap, mapLeft, fold, andThen i operatorów bind.

Either z fpdart

Either<L, R> — lewostronnie skojarzony typ z pakietu fpdart, gdzie Left — błąd, Right — sukces. W przeciwieństwie do wbudowanego Result<T>, Either typuje błąd na poziomie parametru typu (L), co pozwala rozróżniać typy błędów na etapie kompilacji. Pakiet fpdart udostępnia pełny zestaw funkcyjnych kombinatorów: map (Right -> Right), mapLeft (Left -> Left), flatMap (bind — zagnieżdżone Either), andThen (łańcuch bez transformacji), fold (wyjście z Either), getOrElse (wartość domyślna). Dla aplikacji Flutter z podejściem funkcyjnym Either jest standardem 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));
        }
    }
}

// Użycie z fold
final result = await service.fetchUser('42');
result.fold(
    (left)  => showError(left.message),
    (right) => showUser(right),
);

Either<L, R> z fpdart — lewostronnie skojarzony typ: Left — błąd, Right — sukces. fetchUser zwraca Either<AppError, User>, gdzie AppError — sealed class z konkretnymi typami błędów (serverError, networkError). fold obsługuje oba przypadki: pierwsze callback dla Left (błąd), drugie dla Right (sukces). W Dart 3.0 wbudowany Result również obsługuje fold, ale nie udostępnia map/flatMap. Dla łańcuchów Either z fpdart udostępnia map, flatMap (bind), mapLeft, andThen — pełny zestaw funkcyjnych kombinatorów do kompozycji błędów.

Kompozycja Result przez map i flatMap

Główną zaletą Result nad wyjątkami jest kompozycja. Jeśli masz kilka operacji, z których każda może zwrócić błąd, możesz połączyć je w łańcuch przez map i flatMap bez ani jednego zagnieżdżonego if lub try-catch. map transformuje wartość sukcesu: Result.success(x) -> Result.success(f(x)). flatMap (zwany również bind lub andThen) — dla przypadków, gdy transformacja sama zwraca Result: Result.success(x) -> f(x) -> Result<Y>. Jeśli na którymkolwiek kroku wystąpi Failure, kolejne operacje nie są wykonywane — łańcuch jest przerywany.

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

// Kompozycja przez flatMap (andThen w fpdart)
val result = validateToken("abc123")
    .flatMap { fetchProfile("42") }
    .map { it.name }
    .getOrElse { "Guest" }

println(result) // "Alice"

Łańcuch: validateToken -> fetchProfile -> map name -> getOrElse „Guest“. Jeśli validateToken zwróci Left (InvalidToken), łańcuch jest przerywany i zwracane jest „Guest“. Jeśli fetchProfile zwróci Left (UserNotFound) — również „Guest“. Jeśli obie operacje zakończą się sukcesem — nazwa profilu. flatMap pozwala łączyć funkcje Either, z których każda może upaść, w jeden liniowy łańcuch. W tradycyjnym stylu wyjątków ten sam kod wymagałby dwóch zagnieżdżonych try-catch lub sprawdzeń null. getOrElse na końcu — punkt wyjścia z kompozycji, który udostępnia wartość domyślną dla przypadku Failure.

Result vs Wyjątki: kiedy co wybrać

Result i wyjątki — to nie wykluczające się podejścia. Każde ma swój obszar zastosowania i w dobrze zaprojektowanej aplikacji mobilnej używa się obu. Wybór zależy od tego, czy błąd jest oczekiwany (expected) czy nieoczekiwany (unexpected). Result — dla oczekiwanych błędów, które są częścią logiki biznesowej: nieprawidłowy email, niewystarczające środki na koncie, przekroczony limit zapytań. Wyjątki — dla nieoczekiwanych błędów systemowych: utrata sieci, OutOfMemoryError, NullPointerException (których nie powinno być, ale się zdarzają).

KryteriumResult TypeWyjątki (Exception/Error)
Typ błęduOczekiwane (logika biznesowa)Nieoczekiwane (systemowe)
WydajnośćNiski koszt (bez rozwijania stosu)Wysoki koszt (stack unwinding, przechwytywanie StackTrace)
KompozycjaPrzez map/flatMap — liniowe łańcuchyZagnieżdżone try-catch — trudne do czytania
KompilatorWyczerpujące sprawdzanie (switch/when)Tylko checked exceptions w Java
Przepływ wykonaniaNie jest przerywany — błąd jako wartośćPrzerywany do najbliższego catch
TestowanieŁatwe: sprawdzenie wyniku, assert isSuccess/isErrorWymaga assertThrows i obiektów mock
Kiedy używaćWalidacja biznesowa, łańcuchy zapytań, formularzeUtrata sieci, błędy I/O, awarie systemu

Praktyczna zasada: jeśli błąd jest częścią normalnego przepływu pracy aplikacji (użytkownik wprowadził nieprawidłowy email, brak uprawnień dostępu) — używaj Result. Jeśli błąd to sytuacja wyjątkowa (serwer nie odpowiada, skończyła się pamięć) — używaj wyjątków. W programowaniu mobilnym Result na granicach warstw (UseCase -> ViewModel) i wyjątki wewnątrz warstw (API -> Repository) — to popularny wzorzec łączący zalety obu podejść.

Strategia migracji z wyjątków na Result

Migracja istniejącego kodu opartego na wyjątkach na Result powinna być stopniowa. Zacznij od granic warstw: owiń wywołania funkcji throws w Result { try ... } (Swift) lub runCatching { ... } (Kotlin). Następnie zastąp typ zwracany metod Repository i UseCase na Result/Either, pozostawiając wewnętrzną implementację na wyjątkach. Na ostatnim etapie migruj ViewModel: zamiast UiState z wyjątkami używaj sealed class UiState<T> (Loading, Success, Error), gdzie Error przechowuje błąd domenowy, a nie Throwable. Stopniowa migracja pozwala testować każdą warstwę osobno bez globalnego refaktoryzowania.

Często zadawane pytania

Czym Result Type różni się od Optional/Option?

Optional (T?) reprezentuje obecność lub brak wartości — nil oznacza „brak danych“, ale nie mówi dlaczego. Result (Success/Failure) zawiera nie tylko sukces, ale także przyczynę błędu z konkretnym typem. Używaj Optional, gdy brak wartości jest normą (np. opcjonalne pole profilu), Result — gdy potrzebujesz informacji o błędzie.

Jak przekształcić funkcję throws w Result?

W Swift użyj Result { try throwingFunc() } — konstruktor Result przyjmuje throws-zamknięcie. W Kotlin — runCatching { throwingFunc() }, który zwraca Result<T>. W Dart — Result<T>.tryCatch(() => throwingFunc()). Pozwala to łatwo integrować kod oparty na wyjątkach w łańcuchy Result.

Jak obsłużyć błąd w Result bez utraty informacji?

Użyj mapError (Swift) lub mapLeft (Either w Dart/Kotlin) do transformacji typu błędu bez zmiany wartości sukcesu. Jeśli trzeba obsłużyć oba przypadki i zwrócić jedną wartość — użyj fold. Do logowania bez przerywania łańcucha użyj onFailure (Kotlin) lub prefiksowego punktu inspekcji.

Czy można używać Result w Kotlin z korutynami?

Tak, ale ostrożnie. Kotlin Result<T> nie jest zalecany jako typ zwracany suspend-funkcji bezpośrednio ze względu na cechy K2 compiler i refleksję. Używaj własnej sealed class NetworkResult<T> (Success, Error, Loading) do reprezentowania stanów w korutynach. Dla oczekiwanych błędów w logice biznesowej Either z Arrow — potężniejsza alternatywa.

Czym jest fold w kontekście Result?

fold — metoda, która przyjmuje dwa callbacki: onSuccess (dla przypadku sukcesu) i onFailure (dla przypadku błędu), i zwraca jedną wartość dowolnego typu. Jest to odpowiednik wyrażenia switch, ale w postaci funkcji wyższego rzędu. fold — główny punkt wyjścia z łańcuchów Result, gdzie przekształcasz Success/Failure w UiState, string dla użytkownika lub inny Result.

Podsumowanie

  • Result Type — kontener generyczny (Success/Failure) do bezpiecznej obsługi oczekiwanych błędów bez rozwijania stosu
  • W Swift Result<Success, Failure> z Failure: Error zapewnia wyczerpujące sprawdzanie przez switch z błędami typu bezpiecznego
  • W Kotlin Result<T> opakowuje sukces lub Throwable, runCatching — wygodny konstruktor z kodu throws
  • W Dart wbudowany Result<T> (Dart 3.0) i Either<L, R> z fpdart do zaawansowanej kompozycji z map/flatMap
  • Kompozycja przez map (transformacja sukcesu) i flatMap (łańcuch funkcji Result) zastępuje zagnieżdżone try-catch
  • Result vs wyjątki: Result dla oczekiwanych błędów biznesowych, wyjątki dla nieoczekiwanych awarii systemowych
  • Używaj Result na granicach warstw do jawnej i testowalnej obsługi błędów bez przerywania przepływu wykonania

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również