Option en Rust: adiós al null, hola Some y None

El inventor del null, Tony Hoare, lo llamó su "error del billón de dólares". Rust no tiene null. En su lugar, usa Option<T>: un enum que representa de forma explícita la posibilidad de que un valor esté ausente. El compilador te obliga a manejar ambos casos, eliminando toda una categoría de bugs.

La definición de Option

enum Option<T> {
    None,       // ausencia de valor
    Some(T),    // presencia de valor de tipo T
}

Está en el preludio de Rust: no necesitas importarla. Puedes escribir Some(5) o None directamente.

Crear y usar Option con match

fn dividir(a: f64, b: f64) -> Option<f64> {
    if b == 0.0 {
        None
    } else {
        Some(a / b)
    }
}

fn main() {
    match dividir(10.0, 2.0) {
        Some(resultado) => println!("Resultado: {}", resultado),
        None            => println!("División por cero"),
    }

    match dividir(5.0, 0.0) {
        Some(r) => println!("Resultado: {}", r),
        None    => println!("División por cero"),
    }
}

unwrap_or: valor por defecto

let valor = dividir(10.0, 0.0).unwrap_or(0.0);
println!("{}", valor); // 0.0

unwrap_or(x) devuelve el valor interno si es Some, o x si es None. Hay variantes útiles:

  • unwrap_or_default(): usa el valor por defecto del tipo.
  • unwrap_or_else(|| calcular()): llama a una closure si es None.

map: transformar el valor si existe

let longitud: Option<usize> = Some(String::from("hola")).map(|s| s.len());
// Some(4)

let nada: Option<usize> = None::<String>.map(|s| s.len());
// None

map aplica una función al interior si es Some, y propaga None si no hay valor. Nunca panics.

and_then: encadenar operaciones que pueden fallar

fn buscar_usuario(id: u32) -> Option<String> {
    if id == 1 { Some(String::from("Ana")) } else { None }
}

fn obtener_email(nombre: &str) -> Option<String> {
    if nombre == "Ana" { Some(String::from("[email protected]")) } else { None }
}

fn main() {
    let email = buscar_usuario(1)
        .as_deref()
        .and_then(|nombre| obtener_email(nombre));

    println!("{:?}", email); // Some("[email protected]")
}

and_then es como map, pero la función devuelve un Option en lugar de un valor directo. Evita el anidamiento Option<Option<T>>.

if let para un solo caso

if let Some(valor) = dividir(10.0, 2.0) {
    println!("OK: {}", valor);
}
// No hace nada si es None

Propagar None con el operador ?

fn procesar() -> Option<String> {
    let usuario = buscar_usuario(1)?; // si es None, retorna None
    let email   = obtener_email(&usuario)?; // idem
    Some(format!("Email de {}: {}", usuario, email))
}

El operador ? en contexto de Option devuelve None si el valor es None, o extrae el Some y continúa. Elimina el boilerplate de los match anidados.

Cuándo evitar unwrap

unwrap() extrae el valor de Some o hace panic si es None. En producción, úsalo solo cuando tengas la certeza lógica de que el valor existe y quieras que el programa falle ruidosamente si no:

// OK en main o en tests, con certeza lógica
let config = std::env::var("DATABASE_URL").ok().unwrap();

// Mejor en producción:
let config = std::env::var("DATABASE_URL")
    .ok()
    .unwrap_or_else(|| String::from("sqlite://default.db"));

Resumen

  • Option<T> tiene dos variantes: Some(T) y None.
  • Sustituye al null: el tipo hace explícita la posibilidad de ausencia.
  • match e if let para extraer el valor con pattern matching.
  • unwrap_or, map, and_then para trabajar con Option de forma funcional.
  • El operador ? propaga None sin boilerplate.
  • Evita unwrap() en producción salvo que tengas certeza absoluta.

El siguiente artículo cubre match en profundidad: pattern matching exhaustivo con rangos, enums, desestructuración, guards y match como expresión.

COMPARTE ESTE ARTÍCULO

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