Result Type — um tipo contêiner que representa o resultado de uma operação que pode ser concluída com sucesso (Success) ou falha (Failure). Ao contrário das exceções, o Result passa o erro como um valor normal sem desenrolamento de pilha, tornando o tratamento de erros esperados mais seguro e composável. De acordo com Apple Swift Documentation (2026), Result<Success, Failure> em Swift permite encadear operações com tratamento automático de erros através de map e flatMap sem interromper a execução do programa.
Principais pontos
Result Type é um tipo genérico que encapsula o resultado de uma operação que pode ser bem-sucedida ou falhar. Ao contrário das exceções, onde um erro interrompe o fluxo normal e requer desenrolamento de pilha para encontrar um bloco catch, o Result passa o erro como um valor normal — o chamador sempre recebe um objeto e decide como tratá-lo. Isso é especialmente útil para erros esperados: entrada inválida, regras de negócio, rejeição do servidor, onde as exceções seriam um mecanismo muito pesado.
O conceito de Result origina-se da programação funcional, onde tipos semelhantes são chamados de Either (ou Left/Right). Em Swift, o Result tornou-se um tipo padrão no Swift 5.0; em Kotlin, Result<T> apareceu na biblioteca padrão; no Dart 3.0, o Result<T> integrado foi introduzido. Cada implementação fornece métodos para trabalhar com o contêiner: map (transformar o valor de sucesso), flatMap (encadear funções Result), mapError (transformar o erro), fold (tratar ambos os casos). Result Type é a pedra angular do tratamento funcional de erros no desenvolvimento móvel, uma alternativa ao try-catch para cenários esperados.
A principal vantagem do Result é a composabilidade. Você pode combinar várias operações, cada uma das quais pode falhar, em uma única cadeia sem blocos try-catch aninhados. Se alguma operação na cadeia falhar com Failure, toda a cadeia é interrompida e retorna Failure — sem uma única condição if ou bloco catch. Isso torna o código linear e legível, especialmente em cenários com múltiplas requisições sequenciais de API ou verificações de regras de negócio.
Em Swift, Result<Success, Failure> é um enum com dois casos: .success(Success) e .failure(Failure), onde Failure é restrito pelo protocolo Error. O Result do Swift é um tipo totalmente funcional com métodos map, flatMap, mapError e get. get é um método especial: retorna o valor de Success se o resultado for bem-sucedido e lança o erro se for Failure. Isso permite que o Result atue como uma ponte entre os estilos funcional e baseado em exceções: tratar através de map/flatMap em uma cadeia e, no final, usar get com do-catch para integrar com código throws.
O Result do Swift é um enum com parâmetros genéricos, o que permite ao compilador verificar o tratamento exaustivo através de switch ou do-catch. Se você adicionar um novo caso ao enum NetworkError, o compilador produzirá um erro em todas as expressões switch onde esse caso não for tratado. A verificação exaustiva é a principal vantagem do Result sobre as exceções: o compilador garante que todos os erros possíveis sejam considerados em tempo de compilação. Em contraste, as exceções não são verificadas pelo compilador em Swift (apenas throws é declarado, mas não o tipo de erro).
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))
}
}
// Uso com 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 tipificar o erro no nível do tipo: a função fetchUser retorna Result<User, NetworkError>, onde NetworkError é um enum concreto com três casos. O chamador trata cada caso via switch com cobertura exaustiva — o compilador verifica se todas as variantes foram tratadas. O switch exaustivo é uma vantagem chave do Result sobre as exceções: o compilador garante que você não esqueça de tratar .badURL, .requestFailed ou .decodingFailed. Com as exceções, o compilador não exige tratamento, e um catch(.badURL) ausente pode facilmente passar despercebido na revisão de código.
Em Kotlin, Result<T> é um tipo integrado da biblioteca padrão que representa sucesso (T) ou erro (Throwable). Ao contrário do Swift, o Result do Kotlin não permite especificar um tipo de erro concreto — apenas Throwable. Isso é feito por simplicidade, mas requer verificação adicional do tipo de erro através da expressão when. O Result do Kotlin suporta fold (tratar ambos os casos), getOrNull (sucesso ou null), getOrDefault (sucesso ou padrão), map, recover, andThen. Uma peculiaridade do Kotlin: o Result não foi projetado para propagação direta através dos limites de funções — não pode ser usado como tipo de retorno para métodos do Android SDK ou da API Kotlin Coroutines sem adaptação adicional.
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 é um invólucro conveniente em Kotlin que captura qualquer exceção e retorna Result.failure. validateEmail retorna Result.success ou Result.failure dependendo da validação. fold trata ambos os casos de forma compacta: em Success, a próxima função de validação é chamada; em Failure, SignupResult.Error é retornado. Importante: o Result do Kotlin não foi projetado para armazenamento em campos de data class ou transmissão direta através de limites de funções suspend — use classes seladas personalizadas (Success/Error/Loading) para representar estados de UI no Jetpack Compose ou MVVM.
Desde o Dart 3.0, a biblioteca padrão inclui um Result<T> integrado — uma classe selada com dois construtores: T.ok() (sucesso) e Error.error() (erro com Object e StackTrace). Antes do Dart 3.0, os desenvolvedores Flutter usavam Either<L, R> do pacote dartz ou classes seladas personalizadas. O Result integrado no Dart é minimalista: ele não fornece map/flatMap diretamente — essas funções precisam ser implementadas via when ou usando métodos de extensão. Para tratamento funcional sério, o Either do fpdart continua sendo uma solução mais poderosa com suporte para map, flatMap, mapLeft, fold, andThen e operadores bind.
Either<L, R> é um tipo tendencioso à esquerda do pacote fpdart, onde Left é o erro e Right é o sucesso. Ao contrário do Result<T> integrado, o Either tipifica o erro no nível do parâmetro de tipo (L), permitindo a diferenciação do tipo de erro em tempo de compilação. O pacote fpdart fornece um conjunto completo de combinadores funcionais: map (Right -> Right), mapLeft (Left -> Left), flatMap (bind — Either aninhado), andThen (encadear sem transformação), fold (sair do Either), getOrElse (valor padrão). Para aplicativos Flutter com uma abordagem funcional, o Either é o padrão de fato.
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));
}
}
}
// Uso com fold
final result = await service.fetchUser('42');
result.fold(
(left) => showError(left.message),
(right) => showUser(right),
);
Either<L, R> do fpdart é um tipo tendencioso à esquerda: Left — erro, Right — sucesso. fetchUser retorna Either<AppError, User>, onde AppError é uma classe selada com tipos de erro concretos (serverError, networkError). fold trata ambos os casos: o primeiro callback para Left (erro), o segundo para Right (sucesso). No Dart 3.0, o Result integrado também suporta fold, mas não fornece map/flatMap. Para cadeias, o Either do fpdart fornece map, flatMap (bind), mapLeft, andThen — um conjunto completo de combinadores funcionais para composição de erros.
A principal vantagem do Result sobre as exceções é a composição. Se você tem várias operações, cada uma das quais pode falhar, pode encadeá-las usando map e flatMap sem um único if aninhado ou try-catch. map transforma o valor de sucesso: Result.success(x) -> Result.success(f(x)). flatMap (também chamado de bind ou andThen) é para casos onde a própria transformação retorna um Result: Result.success(x) -> f(x) -> Result<Y>. Se alguma etapa falhar com Failure, as operações subsequentes não são executadas — a cadeia é interrompida.
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))
// Composição via flatMap (andThen no fpdart)
val result = validateToken("abc123")
.flatMap { fetchProfile("42") }
.map { it.name }
.getOrElse { "Guest" }
println(result) // "Alice"
Cadeia: validateToken -> fetchProfile -> map name -> getOrElse “Convidado”. Se validateToken retornar Left (InvalidToken), a cadeia é interrompida e retorna “Convidado”. Se fetchProfile retornar Left (UserNotFound) — também “Convidado”. Se ambas as operações forem bem-sucedidas — o nome do perfil. flatMap permite combinar funções Either, cada uma das quais pode falhar, em uma única cadeia linear. No estilo tradicional de exceções, o mesmo código exigiria dois try-catch aninhados ou verificações de null. getOrElse no final é o ponto de saída da composição, fornecendo um valor padrão para o caso de Failure.
Result e exceções não são abordagens mutuamente exclusivas. Cada um tem seu próprio domínio, e em um aplicativo móvel bem projetado, ambos são usados. A escolha depende se o erro é esperado ou inesperado. Result é para erros esperados que fazem parte da lógica de negócios: e-mail inválido, fundos insuficientes, limite de taxa excedido. Exceções são para erros de sistema inesperados: perda de rede, OutOfMemoryError, NullPointerException (que não deveriam acontecer, mas acontecem).
| Critério | Result Type | Exceções (Exception/Error) |
|---|---|---|
| Tipo de erro | Esperados (lógica de negócio) | Inesperados (sistema) |
| Desempenho | Custo baixo (sem desenrolamento de pilha) | Custo alto (desenrolamento de pilha, captura de StackTrace) |
| Composição | Via map/flatMap — cadeias lineares | Try-catch aninhados — difícil de ler |
| Compilador | Verificação exaustiva (switch/when) | Apenas exceções verificadas em Java |
| Fluxo de execução | Não interrompido — erro como valor | Interrompido até o catch mais próximo |
| Teste | Fácil: verificar resultado, afirmar isSuccess/isError | Requer assertThrows e objetos mock |
| Quando usar | Validação de negócio, cadeias de requisições, formulários | Perda de rede, erros de E/S, falhas do sistema |
Regra prática: se o erro faz parte do fluxo normal de trabalho do aplicativo (usuário digitou um e-mail inválido, permissões insuficientes) — use Result. Se o erro é uma situação excepcional (servidor não respondendo, sem memória) — use exceções. No desenvolvimento móvel, Result nos limites das camadas (UseCase -> ViewModel) e exceções dentro das camadas (API -> Repository) é um padrão comum que combina as vantagens de ambas as abordagens.
A migração do código existente baseado em exceções para Result deve ser gradual. Comece pelos limites das camadas: envolva chamadas de funções throws em Result { try ... } (Swift) ou runCatching { ... } (Kotlin). Em seguida, substitua o tipo de retorno dos métodos de Repository e UseCase por Result/Either, mantendo a implementação interna em exceções. Na última etapa, migre a ViewModel: em vez de UiState com exceções, use uma classe selada UiState<T> (Loading, Success, Error), onde Error armazena um erro de domínio, não um Throwable. A migração gradual permite testar cada camada separadamente sem uma refatoração global.
Perguntas frequentes
Optional (T?) representa a presença ou ausência de um valor — nil significa “sem dados” mas não explica por quê. Result (Success/Failure) contém não apenas o sucesso, mas também a razão do erro com um tipo concreto. Use Optional quando a ausência é normal (por exemplo, um campo de perfil opcional) e Result quando precisar de informação do erro.
Em Swift, use Result { try throwingFunc() } — o construtor Result recebe um fechamento throws. Em Kotlin, use runCatching { throwingFunc() }, que retorna Result<T>. Em Dart, use Result<T>.tryCatch(() => throwingFunc()). Isso permite a integração fácil de código baseado em exceções em cadeias de Result.
Use mapError (Swift) ou mapLeft (Either no Dart/Kotlin) para transformar o tipo de erro sem alterar o valor de sucesso. Se precisar tratar ambos os casos e retornar um único valor, use fold. Para registrar sem interromper a cadeia, use onFailure (Kotlin) ou um ponto de inspeção de prefixo.
Sim, mas com cuidado. O Result<T> do Kotlin não é recomendado como tipo de retorno para funções suspend diretamente devido a peculiaridades do compilador K2 e reflexão. Use sua própria classe selada NetworkResult<T> (Success, Error, Loading) para representar estados em corrotinas. Para erros esperados na lógica de negócio, o Either do Arrow é uma alternativa mais poderosa.
fold é um método que recebe dois callbacks: onSuccess (para o caso de sucesso) e onFailure (para o caso de erro), e retorna um único valor de qualquer tipo. É o equivalente a uma expressão switch, mas como uma função de ordem superior. fold é o principal ponto de saída das cadeias de Result, onde você converte Success/Failure em UiState, uma string para o usuário ou outro Result.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também