wandres.dev
EL MODELO DE TIPOS · structs, enums, protocolos

Manejo de errores y typed throws

Errores explícitos con throws, do/try/catch, y la novedad de Swift 6.3: typed throws, que declara exactamente qué errores puede lanzar una función.

⏱ 14 min

En Swift los errores no se ignoran en silencio: el sistema de tipos te obliga a reconocer que algo puede fallar y a decidir qué hacer. Con Swift 6.3, además, puedes declarar exactamente qué errores lanza una función. Errores como ciudadanos de primera clase.

🎯 Al terminar esta lección sabrás
  • Definir y lanzar errores con throws.
  • Capturarlos con do / try / catch.
  • try? y try! y cuándo usarlos.
  • Typed throws (Swift 6.3) y el tipo Result.

Definir errores

Un error es cualquier tipo que conforme al protocolo Error. Los enums son perfectos para ello:

enum ErrorRed: Error {
    case sinConexion
    case codigoInvalido(Int)
    case datosCorruptos
}

Lanzar y propagar: throws

Una función que puede fallar se marca con throws y lanza con throw:

func descargar(_ url: String) throws -> String {
    guard url.hasPrefix("https") else {
        throw ErrorRed.sinConexion
    }
    return "datos de \(url)"
}

Capturar: do / try / catch

Al llamar a una función que lanza, usas try dentro de un bloque do, y capturas con catch:

do {
    let datos = try descargar("http://inseguro.com")
    print(datos)
} catch ErrorRed.sinConexion {
    print("Sin conexión")
} catch {
    print("Otro error: \(error)")   // 'error' está disponible por defecto
}

El catch sin patrón captura cualquier error restante, y te da la variable error automáticamente.

try? y try!

Dos atajos para casos concretos:

// try? convierte el resultado en opcional: nil si lanza
let datos = try? descargar("http://x.com")   // String? → nil si falla

// try! fuerza: si lanza, CRASHEA. Solo si es imposible que falle.
let seguro = try! descargar("https://ok.com")
🛑
try! es como el force unwrap

try! desactiva el manejo de errores y crashea si algo falla. Igual que el ! de los opcionales, resérvalo para lo verdaderamente imposible. En código normal, do/catch o try? son la opción segura.

Typed throws (Swift 6.3)

Novedad clave de Swift 6.3: declarar qué tipo de error lanza una función, no solo que “puede lanzar”:

// Esta función SOLO puede lanzar ErrorRed
func descargar(_ url: String) throws(ErrorRed) -> String {
    guard url.hasPrefix("https") else { throw .sinConexion }
    return "datos"
}
Por qué importa typed throws

Con throws normal, el catch recibe un any Error y tienes que adivinar o comprobar tipos. Con throws(ErrorRed), el compilador sabe exactamente qué puede fallar: en el catch, error ya es un ErrorRed, y el switch sobre sus casos es exhaustivo. Ganas la misma seguridad que con los enums: imposible olvidar manejar un caso de error. Es especialmente valioso en librerías y en código donde el rendimiento importa (embebido), porque evita la indirección del error genérico.

Result: errores como valor

A veces quieres el éxito o el error como un valor que pasar por ahí (en callbacks, por ejemplo). Para eso está Result:

func cargar() -> Result<String, ErrorRed> {
    .success("contenido")
    // o .failure(.datosCorruptos)
}

switch cargar() {
case .success(let datos): print(datos)
case .failure(let err):   print("Falló: \(err)")
}

defer: limpieza garantizada

defer ejecuta código al salir del ámbito, pase lo que pase (éxito o error). Perfecto para liberar recursos:

func procesar() throws {
    print("abrir recurso")
    defer { print("cerrar recurso") }   // se ejecuta siempre al salir
    try descargar("https://x.com")
    // aunque esto lance, el 'defer' cierra el recurso
}
⚔️ Falla con elegancia
  1. Define un enum ErrorValidacion: Error con un par de casos.
  2. Escribe una función throws que valide una edad y lance si es negativa.
  3. Llámala en un do/catch y maneja el error.
  4. Reescríbela con throws(ErrorValidacion) (typed throws) y captura con un switch exhaustivo.
  5. Añade un defer que imprima “validación terminada”.