Al aprender el manejo de errores en Swift, es fácil pensar que hay demasiadas herramientas: throws y do-catch, el signo de interrogación y el de exclamación junto a try, y además el tipo Result. Algunas API lanzan errores, otras devuelven Result y cierto código simplemente ignora todo con try?. ¿Cuál es el estándar?
En realidad, estas herramientas no compiten, sino que se reparten las responsabilidades. El valor predeterminado es throws; las variantes de try forman un espectro de cuánto te importan los errores; y Result complementa el modelo cuando necesitas transportar un error como valor. En la quinta entrega de la serie Fundamentos de Swift trazamos este mapa.
La estructura que cierra primero las rutas de fallo durante la validación de entrada se relaciona con Criterios para elegir entre Swift guard y la salida anticipada anterior.
Fundamentos — Los errores son tipos y lanzar errores es un contrato
El manejo de errores de Swift parte de dos declaraciones. Define el error como un tipo que adopta el protocolo Error y añade throws a la firma de las funciones que pueden fallar.
enum PaymentError: Error {
case insufficientBalance(needed: Int)
case cardExpired
case network(underlying: Error)
}
func pay(amount: Int) throws -> Receipt {
guard balance >= amount else {
throw PaymentError.insufficientBalance(needed: amount - balance)
}
// ...
}
No es casualidad que enum se use tanto para los tipos de error. Como vimos en el artículo sobre opcionales, enum sirve para expresar un conjunto de casos, y los motivos del fallo son precisamente eso. Los valores asociados también permiten transportar contexto, como un importe insuficiente.
Lo más importante es que throws aparece en la firma. El sistema de tipos registra que la función puede fallar. Por eso quien la llama debe usar try, y omitirlo produce un error de compilación. A diferencia de los lenguajes donde no sabes dónde puede producirse una excepción, el código Swift marca con try todos los puntos que pueden fallar. Igual que los opcionales elevan la “ausencia de valor” al tipo, throws eleva la “posibilidad de fallo” a la firma. El principio de seguridad ante todo de la primera entrega se repite aquí.
La forma básica del lado receptor es do-catch.
do {
let receipt = try pay(amount: 50_000)
show(receipt)
} catch PaymentError.insufficientBalance(let needed) {
showTopUp(needed: needed)
} catch {
showError(error) // todo lo demás, error variable proporcionada automáticamente
}
catch usa coincidencia de patrones. Puedes capturar solo ciertos casos, extraer valores asociados y recibir el resto en el último catch, de forma parecida a switch. Es un diseño que encaja con el hecho de que los errores sean enum.
Las tres caras de try — un espectro de atención a los errores
try tiene tres formas, y cada una declara cómo tratar los errores.
try — Lo procesaré o lo propagaré. Es la forma predeterminada. Puedes capturarlo con do-catch o declarar tu función también como throws para propagar el error hacia arriba. La propagación de errores es automática y no requiere código explícito; esa es una de las ventajas ocultas del manejo de errores de Swift. Las funciones de las capas intermedias solo necesitan throws para actuar como tuberías sin coste, y el procesamiento puede hacerse una sola vez en una capa exterior cercana a la UI.
try? — No pasa nada si falla; solo necesito que no haya valor. Convierte el error en un opcional: devuelve un valor si tiene éxito y nil si falla, descartando la información del error. Es adecuado para escenarios de “si no funciona, no pasa nada”, como leer una caché. Lo peligroso es usar try? por hábito. Si lo usas donde necesitas el motivo del fallo, las pistas de depuración desaparecen silenciosamente. Úsalo solo cuando puedas responder afirmativamente a “¿A nadie le importa por qué falló?”
try! — El fallo es un error del programador. Si falla, la aplicación se bloquea de inmediato. Siguiendo exactamente la misma lógica que el desempaquetado forzado !, solo se permite en lugares donde fallar significaría que el código es incorrecto, como cargar un recurso incluido en el bundle de la aplicación. El criterio del artículo sobre opcionales se aplica igual: si nil (aquí, el error) forma parte de un escenario normal, está terminantemente prohibido.
Result — Transportar errores como valores
Si throws es la opción predeterminada, ¿cuándo conviene usar Result? Result es un enum que contiene éxito o fallo.
enum Result<Success, Failure: Error> {
case success(Success)
case failure(Failure)
}
La diferencia decisiva respecto a throws está en el momento y el lugar. throws obliga a procesar o propagar el error inmediatamente al llamar, mientras que Result es un valor normal que puedes guardar, añadir a un array y procesar más tarde. Por eso hay aproximadamente tres situaciones adecuadas para Result.
Primero, las API asíncronas basadas en completion handler. Como vimos en el artículo sobre cierres, el completion handler se ejecuta después de que la función retorne, así que no puede transmitir un error con throws. completion: (Result<Data, NetworkError>) -> Void era el estándar para ese caso. Segundo, cuando necesitas recopilar resultados. Para ejecutar 10 tareas y contabilizar 7 éxitos y 3 fallos, los errores deben ser valores. Tercero, cuando quieres especificar el tipo de fallo. Failure de Result es un tipo concreto, por lo que la firma muestra qué error puede llegar.
Aun así, conviene conocer la dirección del cambio. Desde que async/await se convirtió en el estándar, async throws se encarga de transmitir errores asíncronos, y el primer uso de Result está disminuyendo en el código nuevo. El tercer uso también está siendo absorbido por los typed throws de Swift 6 (throws(PaymentError), SE-0413). Por tanto, el criterio práctico actual es: throws por defecto; Result para situaciones especiales en las que necesitas almacenar, recopilar o transmitir el error como valor.
Por cierto, convertir uno en otro requiere una sola línea. Envuélvelo con Result { try pay(amount: 100) } y extráelo con try result.get(). Como pueden cruzar libremente las fronteras, no hay motivo para aferrarse a uno solo.
Sensibilidad de diseño — Un buen error piensa en quien lo recibe
Salgamos un momento de la sintaxis. La calidad del código de manejo de errores depende en gran medida del diseño de la parte que lanza el error.
Divide los errores en unidades que permitan al llamador actuar de forma diferente. El criterio para subdividir casos es: “¿El receptor trata estos dos de manera diferente?” El saldo insuficiente y la tarjeta caducada deben ser casos distintos porque el mensaje al usuario cambia. En cambio, si la aplicación reintenta igual ante un timeout de TCP y un fallo de DNS, conviene agruparlos como network. Dividir en diez errores los casos que se manejan igual solo aumenta los catch.
El criterio para lanzar un error o devolver un opcional es el mismo. Si la “ausencia” es un resultado cotidiano y previsible, como una consulta a un diccionario, usa un opcional; si algo salió mal y necesitas el motivo, usa throws. Cuando solo hay un motivo obvio para el fallo, a menudo basta con un opcional.
Diseña los mensajes visibles para el usuario junto con el tipo de error. Al adoptar LocalizedError, el propio error puede transportar un mensaje para mostrar; este punto se trata en detalle en otro artículo.
Resultados comprobados mediante ejecución directa
Ejecuté en Apple Swift 6.3.3 una función throwing que convierte una cadena en un entero. Al recibir el fallo con try? solo quedó nil; al capturar el éxito con Result pude volver a conectarlo más tarde con un flujo throwing mediante get().
error=try?-nil:true,result:42
Por esta diferencia, no uso try? cuando el motivo del fallo puede cambiar los registros, los reintentos o el mensaje al usuario. En cambio, en fronteras como la consulta de caché, donde fallo y ausencia se tratan igual, convertir a nil expresa mejor la intención. Solo elijo Result para guardar resultados que no necesitan procesamiento inmediato o para recopilar resultados de varias tareas.
Resumen
- La estructura del manejo de errores de Swift es el protocolo Error (normalmente un enum), una firma throws y la coincidencia de patrones con do-catch. La posibilidad de fallo queda registrada en el sistema de tipos y todos los puntos de fallo se marcan con try.
- Las tres variantes de try declaran una actitud ante los errores: try para procesar o propagar, try? cuando no importa el motivo y try! cuando fallar significa que hay un bug.
- La propagación de errores es automática. Las capas intermedias solo añaden throws y el procesamiento se hace una vez en el exterior.
- Result sirve para almacenar o recopilar errores como valores. La transmisión asíncrona está pasando a async throws, y la especificación explícita del tipo está pasando a typed throws.
- La clave del diseño es dividir los casos de error en unidades donde el llamador se comporte de forma diferente.
Con esto queda completa la trilogía de control de flujo de la serie Fundamentos de Swift: opcionales, guard y manejo de errores. El próximo artículo aborda un fundamento de otro tipo: por qué el String de Swift es especialmente difícil frente a otros lenguajes, empezando por explicar por qué “¿Cuántos caracteres tiene el coreano?” no es una pregunta sencilla.
Seguir leyendo
- Patrones de bridge en Swift: separar abstracciones para evitar la explosión de subclases (recopilación completa de ejemplos)
- [Fundamentos de Swift #3] Guía completa de las propiedades de Swift: 4 criterios para elegir entre stored, computed, lazy y didSet
- [Fundamentos de Swift #6] ¿Por qué Swift String no puede convertirse en text[0]? Guía completa de los clústeres de grafemas (Grapheme Cluster)
Fuentes y verificación
- The Swift Programming Language: Error HandlingSwift.org · Documentación oficial · Consultado 26 de agosto de 2026Respalda: Formas de procesar Error, throw, throws, do-catch y try·try?·try!
- Swift ResultApple Developer Documentation · Documentación oficial · Consultado 26 de agosto de 2026Respalda: API de conversión entre las representaciones success·failure de Result y las expresiones throwing

![Imagen de portada de [Fundamentos de Swift #5] Criterios para elegir entre throws, try y Result](/assets/images/posts/79164702-137a-40ab-b9c5-28127ec6c2df/1.jpg)