Result Type: шта је то, тип-контејнер Result и како ради у мобилном развоју

Аутор: IT Sectr Објављено: 2026-05-26 Време читања: 9 мин

Result Type — тип-контејнер који представља резултат операције која се може завршити успехом (Success) или грешком (Failure). За разлику од изузетака, Result преноси грешку као обичну вредност без одмотавања стека, што чини обраду очекиваних грешака сигурнијом и композибилнијом. Према Apple Swift Documentation (2026), Result<Success, Failure> у Свифт-у омогућава комбиновање ланаца операција са аутоматском обрадом грешака кроз map и flatMap, без прекидања извршења програма.

Главно

  • Result Type — генерички контејнер са два стања: Success (подаци) и Failure (грешка), без одмотавања стека
  • У Свифт-у Result<Success, Failure> — уграђени тип са методама map, flatMap, mapError и get
  • У Котлин-у Result<T> представља успех или Throwable, са функцијама getOrNull, getOrDefault, fold
  • У Дарт-у стандардни Result<T> доступан од Дарт 3.0, такође Either из пакета fpdart
  • Композиција кроз map и flatMap омогућава комбиновање више операција које враћају Result без угнежђених провера

Шта је Result Type?

Result Type је генерички тип који капсулира резултат извршења операције која се може завршити како успешно, тако и грешком. За разлику од изузетака, где грешка прекида нормални ток извршења и захтева одмотавање стека за проналажење catch, Result преноси грешку као обичну вредност — позивајућа страна увек добија објекат и сама одлучује како да поступи с њим. Ово је посебно корисно за очекиване грешке: неважећи унос, пословна правила, одбијање сервера, где би изузетак био превише тежак механизам.

Концепт Result потиче из функционалног програмирања, где се слични типови називају Either (или Left/Right). У Свифт-у, Result је постао стандардни тип у Свифт 5.0, у Котлин-у Result<T> се појавио у стандардној библиотеци, у Дарт 3.0 — уграђени Result<T>. Свака имплементација пружа методе за рад са контејнером: map (трансформација успешне вредности), flatMap (ланац Result функција), mapError (трансформација грешке), fold (обрада оба случаја). Result Type — камен темељац функционалне обраде грешака у мобилном развоју, алтернатива try-catch за очекиване сценарије.

Кључна предност Result-а је композибилност. Можете комбиновати више операција, од којих свака може вратити грешку, у један ланац без угнежђених try-catch. Ако било која операција у ланцу заврши са Failure, цео ланац се прекида и враћа Failure — без иједног услова if или блока catch. Ово чини код линеарним и читљивим, посебно у сценаријима са вишеструким секвенцијалним захтевима ка API-ју или проверама пословних правила.

Result у Свифт-у

У Свифт-у Result<Success, Failure> је еnum са два случаја: .success(Success) и .failure(Failure), где је Failure ограничен протоколом Error. Свифт Result — пуноправни функционални тип са методама map, flatMap, mapError и get. get — посебна метода: враћа вредност Success ако је резултат успешан, и баца грешку ако је Failure. Ово омогућава коришћење Result-а као моста између функционалног и стила заснованог на изузецима: обрађујте кроз map/flatMap у ланцу, а на крају — get кроз do-catch за интеграцију са throws кодом.

Result еnum и исцрпни switch

Свифт Result је еnum са генеричким параметрима, што омогућава компајлеру да провери исцрпну обраду кроз switch или do-catch. Ако у еnum NetworkError додате нови случај, компајлер ће дати грешку у свим switch изразима у којима овај случај није обрађен. Исцрпна провера (exhaustive checking) — главна предност Result-а над изузецима: компајлер гарантује да су све могуће грешке узете у обзир у фази компајлирања. Насупрот томе, изузеци се не проверавају од стране компајлера у Свифт-у (само throws се декларише, али не и тип грешке).

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

// Употреба са 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> омогућава типизовање грешке на нивоу типа: функција fetchUser враћа Result<User, NetworkError>, где је NetworkError конкретан еnum са три случаја. Позивајућа страна обрађује сваки случај кроз switch са исцрпним покривањем — компајлер проверава да ли су све варијанте обрађене. Исцрпни switch — кључна предност Result-а над изузецима: компајлер гарантује да нећете заборавити да обрадите .badURL, .requestFailed или .decodingFailed. У случају изузетака, компајлер не захтева обраду, а необрађени catch(.badURL) лако се превиди у ревизији.

Result у Котлин-у

У Котлин-у Result<T> је уграђени тип из стандардне библиотеке, који представља успех (T) или грешку (Throwable). За разлику од Свифт-а, у Котлин-у Result не дозвољава навођење конкретног типа грешке — само Throwable. Ово је учињено ради једноставности, али захтева додатну проверу типа грешке кроз when израз. Котлин Result подржава функције fold (обрада оба случаја), getOrNull (успех или null), getOrDefault (успех или подразумевана вредност), map, recover, andThen. Посебност Котлин-а: Result није намењен директном пропагирању кроз границе функција — не може се користити као повратни тип за методе Android SDK или Kotlin Coroutines API без додатне адаптације.

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 — згодан омотач у Котлин-у који хвата било који изузетак и враћа Result.failure. validateEmail враћа Result.success или Result.failure у зависности од провере. fold обрађује оба случаја компактно: при Success-у се позива следећа функција валидације, при Failure-у — враћа се SignupResult.Error. Важно: Котлин Result није намењен чувању у пољима data class или директном преношењу кроз границе suspend функција — користите сопствене sealed class (Success/Error/Loading) за представљање UI стања у Jetpack Compose или MVVM.

Result у Дарт-у и Флутер-у

Од Дарт 3.0 у стандардној библиотеци се појавио уграђени Result<T> — sealed class са два конструктора: T.ok() (успех) и Error.error() (грешка са Object и StackTrace). Пре Дарт 3.0, Флутер програмери су користили Either<L, R> из пакета dartz или сопствене sealed class. Уграђени Result у Дарт-у је минималистичан: не пружа map/flatMap директно — ове функције треба имплементирати кроз when или коришћењем extension methods. За озбиљну функционалну обраду, Either из fpdart-а остаје моћније решење са подршком за map, flatMap, mapLeft, fold, andThen и bind операторе.

Either из fpdart-а

Either<L, R> — лево-асоцирани тип из пакета fpdart, где је Left — грешка, Right — успех. За разлику од уграђеног Result<T>, Either типизује грешку на нивоу параметра типа (L), што омогућава разликовање типова грешака у фази компајлирања. Пакет fpdart пружа пун скуп функционалних комбинатора: map (Right -> Right), mapLeft (Left -> Left), flatMap (bind — угнежђени Either), andThen (ланац без трансформације), fold (излаз из Either-а), getOrElse (подразумевана вредност). За Флутер апликације са функционалним приступом, Either је де факто стандард.

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

// Употреба са fold
final result = await service.fetchUser('42');
result.fold(
    (left)  => showError(left.message),
    (right) => showUser(right),
);

Either<L, R> из fpdart-а — лево-асоцирани тип: Left — грешка, Right — успех. fetchUser враћа Either<AppError, User>, где је AppError — sealed class са конкретним типовима грешака (serverError, networkError). fold обрађује оба случаја: први callback за Left (грешка), други за Right (успех). У Дарт 3.0 уграђени Result такође подржава fold, али не пружа map/flatMap. За ланце, Either из fpdart-а пружа map, flatMap (bind), mapLeft, andThen — пун скуп функционалних комбинатора за композицију грешака.

Композиција Result кроз map и flatMap

Главна предност Result-а над изузецима је композиција. Ако имате више операција, од којих свака може вратити грешку, можете их комбиновати у ланац кроз map и flatMap без иједног угнежђеног if или try-catch. map трансформише успешну вредност: Result.success(x) -> Result.success(f(x)). flatMap (такође назван bind или andThen) — за случајеве када сама трансформација враћа Result: Result.success(x) -> f(x) -> Result<Y>. Ако у било ком кораку дође до Failure, наредне операције се не извршавају — ланац се прекида.

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

// Композиција кроз flatMap (andThen у fpdart-у)
val result = validateToken("abc123")
    .flatMap { fetchProfile("42") }
    .map { it.name }
    .getOrElse { "Guest" }

println(result) // "Alice"

Ланац: validateToken -> fetchProfile -> map name -> getOrElse „Guest". Ако validateToken врати Left (InvalidToken), ланац се прекида и враћа се „Guest". Ако fetchProfile врати Left (UserNotFound) — такође „Guest". Ако обе операције буду успешне — име профила. flatMap омогућава комбиновање Either функција, од којих свака може да падне, у један линеарни ланац. У традиционалном стилу изузетака, исти би код захтевао два угнежђена try-catch или провере null. getOrElse на крају — тачка изласка из композиције, која пружа подразумевану вредност за случај Failure.

Result vs Изузеци: када шта изабрати

Result и изузеци — нису међусобно искључиви приступи. Сваки има своју област примене, и у добро дизајнираној мобилној апликацији користе се оба. Избор зависи од тога да ли је грешка очекивана (expected) или неочекивана (unexpected). Result — за очекиване грешке које су део пословне логике: неважећи емаил, недовољно средстава на рачуну, прекорачен лимит захтева. Изузеци — за неочекиване системске грешке: губитак мреже, OutOfMemoryError, NullPointerException (којих не би требало да буде, али се дешавају).

КритеријумResult TypeИзузеци (Exception/Error)
Тип грешкеОчекиване (пословна логика)Неочекиване (системске)
ПерформансеНиска цена (без одмотавања стека)Висока цена (stack unwinding, хватање StackTrace)
КомпозицијаКроз map/flatMap — линеарни ланциУгнежђени try-catch — тешко за читање
КомпајлерИсцрпна провера (switch/when)Само checked exceptions у Java
Ток извршењаНе прекида се — грешка као вредностПрекида се до најближег catch
ТестирањеЛако: провера резултата, assert isSuccess/isErrorЗахтева assertThrows и mock објекте
Када користитиПословна валидација, ланци захтева, формеГубитак мреже, I/O грешке, падови система

Практично правило: ако је грешка део нормалног тока рада апликације (корисник је унео неважећи емаил, нема довољно права приступа) — користите Result. Ако је грешка изузетна ситуација (сервер не одговара, понестало је меморије) — користите изузетке. У мобилном развоју Result на границама слојева (UseCase -> ViewModel) и изузеци унутар слојева (API -> Repository) — уобичајени образац који комбинује предности оба приступа.

Стратегија миграције са изузетака на Result

Миграција постојећег кода заснованог на изузецима на Result треба да буде постепена. Почните са границама слојева: умотајте позиве throws функција у Result { try ... } (Swift) или runCatching { ... } (Kotlin). Затим замените повратни тип метода Repository и UseCase са Result/Either, остављајући унутрашњу имплементацију на изузецима. У последњој фази мигрирајте ViewModel: уместо UiState са изузецима користите sealed class UiState<T> (Loading, Success, Error), где Error чува доменску грешку, а не Throwable. Постепена миграција омогућава тестирање сваког слоја одвојено без глобалног рефакторисања.

Често постављана питања

По чему се Result Type разликује од Optional/Option?

Optional (T?) представља присуство или одсуство вредности — nil значи „нема података", али не каже зашто. Result (Success/Failure) садржи не само успех, већ и узрок грешке са конкретним типом. Користите Optional када је одсуство вредности норма (нпр. опционално поље профила), Result — када вам је потребна информација о грешци.

Како претворити throws функцију у Result?

У Свифт-у користите Result { try throwingFunc() } — конструктор Result-а прихвата throws затварање. У Котлин-у — runCatching { throwingFunc() }, који враћа Result<T>. У Дарт-у — Result<T>.tryCatch(() => throwingFunc()). Ово омогућава лаку интеграцију кода заснованог на изузецима у Result ланце.

Како обрадити грешку у Result-у без губитка информација?

Користите mapError (Swift) или mapLeft (Either у Дарт/Котлин) за трансформацију типа грешке без промене успешне вредности. Ако треба да обрадите оба случаја и вратите јединствену вредност — користите fold. За евидентирање без прекидања ланца користите onFailure (Kotlin) или префиксни inspection point.

Може ли се Result користити у Котлин-у са корутинама?

Да, али опрезно. Котлин Result<T> се не препоручује као повратни тип suspend функција директно због особености K2 компајлера и рефлексије. Користите сопствену sealed class NetworkResult<T> (Success, Error, Loading) за представљање стања у корутинама. За очекиване грешке у пословној логици, Either из Arrow-а је моћнија алтернатива.

Шта је fold у контексту Result-а?

fold — метода која прима два callback: onSuccess (за успешан случај) и onFailure (за случај грешке), и враћа јединствену вредност било ког типа. Ово је еквивалент switch израза, али у облику функције вишег реда. fold — главна тачка изласка из Result ланаца, где претварате Success/Failure у UiState, стринг за корисника или други Result.

Резиме

  • Result Type — генерички контејнер (Success/Failure) за безбедну обраду очекиваних грешака без одмотавања стека
  • У Свифт-у Result<Success, Failure> са Failure: Error обезбеђује исцрпну проверу кроз switch са type-safe грешкама
  • У Котлин-у Result<T> умотава успех или Throwable, runCatching — згодан конструктор из throws кода
  • У Дарт-у уграђени Result<T> (Дарт 3.0) и Either<L, R> из fpdart-а за напредну композицију са map/flatMap
  • Композиција кроз map (трансформација успеха) и flatMap (ланац Result функција) замењује угнежђене try-catch
  • Result vs изузеци: Result за очекиване пословне грешке, изузеци за неочекиване системске отказе
  • Користите Result на границама слојева за експлицитну и тестирану обраду грешака без прекидања тока извршења

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође