Result Type — un tipo contenedor que representa el resultado de una operación que puede completarse con éxito (Success) o error (Failure). A diferencia de las excepciones, Result transmite el error como un valor normal sin desenrollado de pila, lo que hace que el manejo de errores esperados sea más seguro y componible. Según Apple Swift Documentation (2026), Result<Success, Failure> en Swift permite encadenar operaciones con manejo automático de errores mediante map y flatMap sin interrumpir la ejecución del programa.
Puntos clave
Result Type es un tipo genérico que encapsula el resultado de una operación que puede finalizar exitosamente o con un error. A diferencia de las excepciones, donde un error interrumpe el flujo normal y requiere desenrollado de pila para encontrar un bloque catch, Result transmite el error como un valor normal — el llamador siempre recibe un objeto y decide cómo manejarlo. Esto es especialmente útil para errores esperados: entrada inválida, reglas de negocio, rechazo del servidor, donde las excepciones serían un mecanismo demasiado pesado.
El concepto de Result proviene de la programación funcional, donde tipos similares se llaman Either (o Left/Right). En Swift, Result se convirtió en un tipo estándar en Swift 5.0; en Kotlin, Result<T> apareció en la biblioteca estándar; en Dart 3.0, se introdujo Result<T> integrado. Cada implementación proporciona métodos para trabajar con el contenedor: map (transformar el valor de éxito), flatMap (encadenar funciones Result), mapError (transformar el error), fold (manejar ambos casos). Result Type es una piedra angular del manejo funcional de errores en el desarrollo móvil, una alternativa a try-catch para escenarios esperados.
La ventaja clave de Result es la componibilidad. Puede combinar múltiples operaciones, cada una de las cuales puede fallar, en una sola cadena sin bloques try-catch anidados. Si alguna operación en la cadena falla con Failure, toda la cadena se interrumpe y devuelve Failure — sin una sola condición if o bloque catch. Esto hace que el código sea lineal y legible, especialmente en escenarios con múltiples solicitudes API secuenciales o comprobaciones de reglas de negocio.
En Swift, Result<Success, Failure> es un enum con dos casos: .success(Success) y .failure(Failure), donde Failure está restringido por el protocolo Error. Result en Swift es un tipo completamente funcional con métodos map, flatMap, mapError y get. get es un método especial: devuelve el valor de Success si el resultado es exitoso, y lanza el error si es Failure. Esto permite que Result actúe como un puente entre los estilos funcional y basado en excepciones: manejar mediante map/flatMap en una cadena, y al final usar get con do-catch para integrarse con código throws.
Result en Swift es un enum con parámetros genéricos, lo que permite al compilador verificar el manejo exhaustivo mediante switch o do-catch. Si agrega un nuevo caso al enum NetworkError, el compilador generará un error en todas las expresiones switch donde ese caso no se maneje. La comprobación exhaustiva es la principal ventaja de Result sobre las excepciones: el compilador garantiza que todos los errores posibles se tengan en cuenta en tiempo de compilación. En contraste, las excepciones no son verificadas por el compilador en Swift (solo se declara throws, pero no el tipo de error).
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 con 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 el error a nivel de tipo: la función fetchUser devuelve Result<User, NetworkError>, donde NetworkError es un enum concreto con tres casos. El llamador maneja cada caso mediante switch con cobertura exhaustiva — el compilador verifica que todas las variantes estén manejadas. El switch exhaustivo es una ventaja clave de Result sobre las excepciones: el compilador garantiza que no olvide manejar .badURL, .requestFailed o .decodingFailed. Con las excepciones, el compilador no exige manejo, y un catch(.badURL) faltante puede pasarse por alto fácilmente en la revisión de código.
En Kotlin, Result<T> es un tipo integrado de la biblioteca estándar que representa éxito (T) o error (Throwable). A diferencia de Swift, Result en Kotlin no permite especificar un tipo de error concreto — solo Throwable. Esto se hace por simplicidad pero requiere una verificación adicional del tipo de error mediante la expresión when. Result en Kotlin admite fold (manejar ambos casos), getOrNull (éxito o null), getOrDefault (éxito o valor predeterminado), map, recover, andThen. Una peculiaridad de Kotlin: Result no está diseñado para propagación directa a través de los límites de funciones — no se puede usar como tipo de retorno para métodos de Android SDK o la API de Kotlin Coroutines sin adaptación 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 es un envoltorio conveniente en Kotlin que captura cualquier excepción y devuelve Result.failure. validateEmail devuelve Result.success o Result.failure según la validación. fold maneja ambos casos de forma compacta: en Success, se llama a la siguiente función de validación; en Failure, se devuelve SignupResult.Error. Importante: Result en Kotlin no está diseñado para almacenarse en campos de data class ni para transmisión directa a través de los límites de funciones suspend — use clases selladas personalizadas (Success/Error/Loading) para representar estados de UI en Jetpack Compose o MVVM.
Desde Dart 3.0, la biblioteca estándar incluye un Result<T> integrado — una clase sellada con dos constructores: T.ok() (éxito) y Error.error() (error con Object y StackTrace). Antes de Dart 3.0, los desarrolladores de Flutter usaban Either<L, R> del paquete dartz o clases selladas personalizadas. El Result integrado en Dart es minimalista: no proporciona map/flatMap directamente — estas funciones deben implementarse mediante when o usando métodos de extensión. Para un manejo funcional serio, Either de fpdart sigue siendo una solución más potente con soporte para map, flatMap, mapLeft, fold, andThen y operadores bind.
Either<L, R> es un tipo sesgado a la izquierda del paquete fpdart, donde Left es el error y Right es el éxito. A diferencia de Result<T> integrado, Either tipifica el error a nivel del parámetro de tipo (L), lo que permite la diferenciación de tipos de error en tiempo de compilación. El paquete fpdart proporciona un conjunto completo de combinadores funcionales: map (Right -> Right), mapLeft (Left -> Left), flatMap (bind — Either anidado), andThen (encadenar sin transformación), fold (salir de Either), getOrElse (valor predeterminado). Para aplicaciones Flutter con un enfoque funcional, Either es el estándar de facto.
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 con fold
final result = await service.fetchUser('42');
result.fold(
(left) => showError(left.message),
(right) => showUser(right),
);
Either<L, R> de fpdart es un tipo sesgado a la izquierda: Left — error, Right — éxito. fetchUser devuelve Either<AppError, User>, donde AppError es una clase sellada con tipos de error concretos (serverError, networkError). fold maneja ambos casos: el primer callback para Left (error), el segundo para Right (éxito). En Dart 3.0, el Result integrado también admite fold, pero no proporciona map/flatMap. Para cadenas, Either de fpdart proporciona map, flatMap (bind), mapLeft, andThen — un conjunto completo de combinadores funcionales para la composición de errores.
La principal ventaja de Result sobre las excepciones es la composición. Si tiene varias operaciones, cada una de las cuales puede fallar, puede encadenarlas usando map y flatMap sin un solo if anidado o try-catch. map transforma el valor de éxito: Result.success(x) -> Result.success(f(x)). flatMap (también llamado bind o andThen) es para casos donde la transformación misma devuelve un Result: Result.success(x) -> f(x) -> Result<Y>. Si algún paso falla con Failure, las operaciones subsiguientes no se ejecutan — la cadena se interrumpe.
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))
// Composición mediante flatMap (andThen en fpdart)
val result = validateToken("abc123")
.flatMap { fetchProfile("42") }
.map { it.name }
.getOrElse { "Guest" }
println(result) // "Alice"
Cadena: validateToken -> fetchProfile -> map name -> getOrElse “Invitado”. Si validateToken devuelve Left (InvalidToken), la cadena se interrumpe y devuelve “Invitado”. Si fetchProfile devuelve Left (UserNotFound) — también “Invitado”. Si ambas operaciones tienen éxito — el nombre del perfil. flatMap permite combinar funciones Either, cada una de las cuales puede fallar, en una sola cadena lineal. En el estilo tradicional de excepciones, el mismo código requeriría dos try-catch anidados o comprobaciones de null. getOrElse al final es el punto de salida de la composición, que proporciona un valor predeterminado para el caso de Failure.
Result y las excepciones no son enfoques mutuamente excluyentes. Cada uno tiene su propio dominio, y en una aplicación móvil bien diseñada, se usan ambos. La elección depende de si el error es esperado o inesperado. Result es para errores esperados que son parte de la lógica de negocio: correo electrónico inválido, fondos insuficientes, límite de solicitudes excedido. Excepciones son para errores de sistema inesperados: pérdida de red, OutOfMemoryError, NullPointerException (que no deberían ocurrir pero suceden).
| Criterio | Result Type | Excepciones (Exception/Error) |
|---|---|---|
| Tipo de error | Esperados (lógica de negocio) | Inesperados (sistema) |
| Rendimiento | Costo bajo (sin desenrollado de pila) | Costo alto (desenrollado de pila, captura de StackTrace) |
| Composición | Mediante map/flatMap — cadenas lineales | Try-catch anidados — difícil de leer |
| Compilador | Comprobación exhaustiva (switch/when) | Solo excepciones verificadas en Java |
| Flujo de ejecución | No se interrumpe — error como valor | Se interrumpe hasta el catch más cercano |
| Pruebas | Fácil: verificar resultado, assert isSuccess/isError | Requiere assertThrows y objetos mock |
| Cuándo usar | Validación de negocio, cadenas de solicitudes, formularios | Pérdida de red, errores de E/S, caídas del sistema |
Regla práctica: si el error es parte del flujo normal de trabajo de la aplicación (el usuario ingresó un correo inválido, permisos insuficientes) — use Result. Si el error es una situación excepcional (servidor no responde, sin memoria) — use excepciones. En el desarrollo móvil, Result en los límites de las capas (UseCase -> ViewModel) y excepciones dentro de las capas (API -> Repository) es un patrón común que combina las ventajas de ambos enfoques.
La migración del código existente basado en excepciones a Result debe ser gradual. Comience con los límites de las capas: envuelva las llamadas a funciones throws en Result { try ... } (Swift) o runCatching { ... } (Kotlin). Luego reemplace el tipo de retorno de los métodos de Repository y UseCase con Result/Either, manteniendo la implementación interna con excepciones. En el último paso, migre el ViewModel: en lugar de UiState con excepciones, use una clase sellada UiState<T> (Loading, Success, Error), donde Error almacena un error de dominio, no un Throwable. La migración gradual permite probar cada capa por separado sin una refactorización global.
Preguntas frecuentes
Optional (T?) representa la presencia o ausencia de un valor — nil significa “sin datos” pero no explica por qué. Result (Success/Failure) contiene no solo el éxito sino también la razón del error con un tipo concreto. Use Optional cuando la ausencia es normal (por ejemplo, un campo de perfil opcional), y Result cuando necesite información del error.
En Swift, use Result { try throwingFunc() } — el constructor de Result toma un cierre que lanza. En Kotlin, use runCatching { throwingFunc() }, que devuelve Result<T>. En Dart, use Result<T>.tryCatch(() => throwingFunc()). Esto permite una integración fácil del código basado en excepciones en cadenas de Result.
Use mapError (Swift) o mapLeft (Either en Dart/Kotlin) para transformar el tipo de error sin cambiar el valor de éxito. Si necesita manejar ambos casos y devolver un único valor, use fold. Para registrar sin interrumpir la cadena, use onFailure (Kotlin) o un punto de inspección de prefijo.
Sí, pero con precaución. No se recomienda Result<T> de Kotlin como tipo de retorno para funciones suspend directamente debido a particularidades del compilador K2 y la reflexión. Use su propia clase sellada NetworkResult<T> (Success, Error, Loading) para representar estados en corrutinas. Para errores esperados en la lógica de negocio, Either de Arrow es una alternativa más potente.
fold es un método que toma dos callbacks: onSuccess (para el caso de éxito) y onFailure (para el caso de error), y devuelve un único valor de cualquier tipo. Es el equivalente de una expresión switch pero como una función de orden superior. fold es el punto de salida principal de las cadenas de Result, donde convierte Success/Failure en UiState, una cadena para el usuario u otro Result.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también