Result Type: qué es, el tipo contenedor Result y cómo funciona en el desarrollo móvil

Autor: IT Sectr Publicado: 2026-05-26 Tiempo de lectura: 9 min

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 — un contenedor genérico con dos estados: Success (datos) y Failure (error), sin desenrollado de pila
  • En Swift Result<Success, Failure> — un tipo integrado con métodos map, flatMap, mapError y get
  • En Kotlin Result<T> representa éxito o Throwable, con funciones getOrNull, getOrDefault, fold
  • En Dart Result<T> estándar disponible desde Dart 3.0, también Either del paquete fpdart
  • Composición mediante map y flatMap permite combinar múltiples operaciones que devuelven Result sin comprobaciones anidadas

¿Qué es Result Type?

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.

Result en Swift

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.

Enum Result y switch exhaustivo

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

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

// 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.

Result en Kotlin

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.

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

Result en Dart y Flutter

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 de fpdart

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.

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

// 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.

Composición de Result mediante map y flatMap

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.

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

// 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 vs Excepciones: cuándo elegir cada uno

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

CriterioResult TypeExcepciones (Exception/Error)
Tipo de errorEsperados (lógica de negocio)Inesperados (sistema)
RendimientoCosto bajo (sin desenrollado de pila)Costo alto (desenrollado de pila, captura de StackTrace)
ComposiciónMediante map/flatMap — cadenas linealesTry-catch anidados — difícil de leer
CompiladorComprobación exhaustiva (switch/when)Solo excepciones verificadas en Java
Flujo de ejecuciónNo se interrumpe — error como valorSe interrumpe hasta el catch más cercano
PruebasFácil: verificar resultado, assert isSuccess/isErrorRequiere assertThrows y objetos mock
Cuándo usarValidación de negocio, cadenas de solicitudes, formulariosPé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.

Estrategia de migración de excepciones a Result

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

¿En qué se diferencia Result Type de Optional/Option?

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.

¿Cómo convertir una función throws a Result?

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.

¿Cómo manejar un error en Result sin perder información?

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.

¿Se puede usar Result en Kotlin con corrutinas?

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.

¿Qué es fold en el contexto de Result?

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

  • Result Type — un contenedor genérico (Success/Failure) para el manejo seguro de errores esperados sin desenrollado de pila
  • En Swift Result<Success, Failure> con Failure: Error proporciona comprobación exhaustiva mediante switch con errores type-safe
  • En Kotlin Result<T> envuelve éxito o Throwable, runCatching es un constructor conveniente desde código throws
  • En Dart Result<T> integrado (Dart 3.0) y Either<L, R> de fpdart para composición avanzada con map/flatMap
  • Composición mediante map (transformar éxito) y flatMap (encadenar funciones Result) reemplaza try-catch anidados
  • Result vs excepciones: Result para errores de negocio esperados, excepciones para fallos de sistema inesperados
  • Use Result en los límites de las capas para un manejo de errores explícito y comprobable sin interrumpir el flujo de ejecución

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.

Discutir el proyecto

Lea también