Gestión de errores en Gleam: Result, Option y el flujo sin excepciones

Gleam no tiene excepciones. No hay try/catch, no hay throw, no hay rescue. Para algunos que vienen de Python, Java o Ruby esto puede parecer una limitación, pero en la práctica es una decisión de diseño que simplifica enormemente el razonamiento sobre el flujo del programa.

En Gleam los errores son valores. Se representan con dos tipos del núcleo del lenguaje: Result(ok, err) para operaciones que pueden fallar y Option(a) para valores que pueden no existir. El compilador obliga a manejarlos explícitamente.

Result: Ok o Error, sin sorpresas

Result(ok, err) es un tipo con dos variantes:

  • Ok(value) — la operación tuvo éxito y aquí está el valor
  • Error(reason) — la operación falló y aquí está la razón
import gleam/int

pub fn divide(a: Int, b: Int) -> Result(Int, String) {
  case b {
    0 -> Error("División por cero")
    _ -> Ok(a / b)
  }
}

pub fn main() {
  case divide(10, 2) {
    Ok(result) -> io.println("Resultado: " <> int.to_string(result))
    Error(msg)  -> io.println("Error: " <> msg)
  }
}

El compilador no te deja usar el valor de un Result sin comprobar primero si es Ok o Error. Eso elimina los «olvidé manejar el error» que son tan comunes en lenguajes dinámicos.

Option: Some o None en lugar de null

Option(a) tiene dos variantes: Some(a) cuando hay valor y None cuando no lo hay. No existe null ni nil en Gleam:

import gleam/list

pub fn find_user(id: Int, users: List(User)) -> Option(User) {
  list.find(users, fn(u) { u.id == id })
}

pub fn main() {
  let users = [User(1, "Ana"), User(2, "Luis")]

  case find_user(1, users) {
    Some(user) -> io.println("Encontrado: " <> user.name)
    None       -> io.println("No existe")
  }
}

gleam/result: transformar sin anidar

La librería gleam/result tiene funciones para trabajar con Result sin tener que anidar case uno dentro de otro. Las más usadas:

result.map — transforma el valor si es Ok, pasa el Error sin tocarlo:

import gleam/result

let doubled = result.map(divide(10, 2), fn(n) { n * 2 })
// Ok(10) si divide devuelve Ok(5)
// Error("División por cero") si divide falla

result.try — encadena operaciones que pueden fallar (equivale al bind monádico):

import gleam/result
import gleam/int

pub fn parse_and_double(s: String) -> Result(Int, String) {
  int.parse(s)
  |> result.map_error(fn(_) { "No es un número: " <> s })
  |> result.try(fn(n) { divide(n * 2, 1) })
}

result.unwrap — extrae el valor Ok con un default si es Error:

let value = result.unwrap(parse_and_double("42"), 0)
// 84 si el parse tiene éxito, 0 si falla

El operador use para desanchar

Cuando tienes varias operaciones encadenadas con result.try, el código puede volverse difícil de leer. El operador use de Gleam aplana ese anidamiento:

// Sin use: anidado
pub fn process(input: String) -> Result(String, String) {
  result.try(parse_number(input), fn(n) {
    result.try(validate_range(n), fn(valid) {
      result.map(format_output(valid), fn(output) {
        output
      })
    })
  })
}

// Con use: secuencial y legible
pub fn process(input: String) -> Result(String, String) {
  use n     <- result.try(parse_number(input))
  use valid <- result.try(validate_range(n))
  use output <- result.map(format_output(valid))
  output
}

El operador use hace que el código parezca secuencial aunque internamente sea funcional. Si cualquiera de los pasos devuelve un Error, la función entera devuelve ese Error sin ejecutar el resto.

Errores en el contexto de OTP: let it crash

Gleam no tiene excepciones para el código de aplicación, pero la BEAM sí tiene el concepto de proceso que falla. Cuando un proceso falla por una condición inesperada (un assert que no se cumple, un error de la VM), el proceso muere y el supervisor lo reinicia en estado limpio.

// let assert: fuerza el pattern matching, el proceso falla si no coincide
pub fn must_parse(s: String) -> Int {
  let assert Ok(n) = int.parse(s)
  n
}

Usa let assert solo para invariantes que nunca deberían fallar: configuración que se carga al arrancar, datos que ya has validado antes. Para todo lo demás, usa Result y maneja el error de forma explícita.

Por qué este modelo importa

El modelo de errores de Gleam hace que el flujo de errores sea siempre visible en los tipos. Si una función puede fallar, su tipo de retorno incluye Result. Si el valor puede no existir, el tipo incluye Option. No hay sorpresas ocultas detrás de una excepción que alguien olvidó documentar.

Para los que vienen de Elixir, el patrón {:ok, value} | {:error, reason} es la convención pero no está forzado. En Gleam sí lo está: el compilador no te deja ignorar un Result. Esa pequeña diferencia tiene un impacto grande en la calidad del código a largo plazo.

El siguiente artículo de la serie cubre el ecosistema de paquetes de Gleam en Hex y las librerías más usadas en 2026.

Imagen: Pexels / Pixabay

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP