Option<T> es un tipo que representa presencia o ausencia sin usar null.
Tony Hoare, que introdujo las referencias nulas en ALGOL W, llamó a su creación “el error de los mil millones de dólares”. En muchos lenguajes, una referencia o puntero nulo puede confundirse con uno válido y provocar una excepción o un acceso inválido si olvidamos comprobarlo.
Las referencias seguras y los tipos ordinarios de Rust no admiten null como valor válido. Los punteros crudos sí pueden ser nulos, pero se usan fuera de estas garantías y son habituales sobre todo en interoperabilidad.
Si una variable es de tipo String, te garantizo que tiene un string válido dentro. Si quieres expresar que podría no haber nada, Rust te obliga a envolverlo en un tipo especial: Option.
¿Qué es Option?
Option<T> es un Enum definido en la librería estándar que tiene solo dos variantes:
enum Option<T> {
Some(T), // Contiene un valor de tipo T
None, // No contiene nada (equivalente a null, pero seguro)
}Está incluido en el preludio, así que puedes usar Some y None directamente sin importar nada.
La gran diferencia: seguridad de tipos
Imagina que tienes un número entero i32. En Rust, ese número SIEMPRE es válido.
Si quieres un número opcional, tienes un Option<i32>.
La clave es esta: no son el mismo tipo. No puedes sumarlos por error.
let x: i8 = 5;
let y: Option<i8> = Some(5);
// ❌ ERROR: no se puede sumar i8 + Option<i8>
// let suma = x + y;El compilador te dice: “Oye, y podría estar vacío (None). No sé cómo sumar ‘nada’ a 5. Primero asegúrate de que hay un dato dentro”.
Esto evita confundir directamente la ausencia con el valor. Aún podemos provocar un pánico si forzamos un None con unwrap, pero el caso vacío es visible en el tipo.
Crear valores Option
Es muy sencillo instanciar este enum:
fn main() {
let algun_numero = Some(5);
let algun_texto = Some("Hola");
// Si es None, debemos especificar el tipo, porque Rust no puede inferirlo
let numero_ausente: Option<i32> = None;
}Extraer el valor
Tienes una caja (Option) y quieres el regalo de dentro. ¿Cómo lo sacas? Tenemos varias estrategias dependiendo de cuánto riesgo o control queramos.
La forma exhaustiva: match
Es la forma más segura y explícita. Manejas ambos casos.
fn saludo(nombre: Option<&str>) {
match nombre {
Some(n) => println!("Hola, {}!", n),
None => println!("Hola, desconocido."),
}
}La forma arriesgada: unwrap y expect
Igual que con Result, estos métodos te dan el valor si existe, o causan un panic! si es None.
let x = Some(10);
let valor = x.unwrap(); // 10
let vacio: Option<i32> = None;
// let valor = vacio.expect("¡Debía haber un número!"); // 💥 PánicoUsa esto solo si estás 100% seguro de que no es None.
Un valor predeterminado con unwrap_or
“Dame el valor, y si no hay nada, dame este valor por defecto”. Esta es una de las formas más útiles y limpias.
let x = Some(10);
let y = None;
// Si hay dato lo usa, si no, usa el 0
let a = x.unwrap_or(0); // 10
let b = y.unwrap_or(0); // 0También existe unwrap_or_else(|| calcular_costoso()), que acepta un closure para calcular el valor por defecto solo si es necesario (evaluación perezosa).
El atajo de control de flujo: if let
Si solo te importa el caso Some y quieres ignorar el None:
if let Some(valor) = obtener_configuracion() {
println!("Configuración cargada: {}", valor);
}Transformar Option con combinadores
El estilo funcional de Rust encaja muy bien con Option.
Imagina que tienes un Option<i32> y, si tiene valor, quieres multiplicarlo por 2. Si es None, que siga siendo None.
Forma imperativa (aburrida):
let x = Some(5);
let resultado;
match x {
Some(i) => resultado = Some(i * 2),
None => resultado = None,
}Forma funcional (elegante):
El método .map() aplica una función al contenido si es Some, y no hace nada si es None.
let x = Some(5);
let resultado = x.map(|i| i * 2); // Devuelve Some(10)
let y: Option<i32> = None;
let nada = y.map(|i| i * 2); // Devuelve NonePodemos encadenar operaciones sin miedo a excepciones:
fn longitud_del_nombre(nombre: Option<String>) -> Option<usize> {
nombre
.map(|s| s.trim().to_string()) // Quita espacios
.filter(|s| !s.is_empty()) // Si queda vacío, lo convierte a None
.map(|s| s.len()) // Calcula longitud
}El operador ? con Option
Al igual que con Result, el operador ? funciona con Option.
Si el valor es Some, lo extrae. Si es None, retorna None inmediatamente desde la función actual.
fn primer_elemento_duplicado(lista: Vec<i32>) -> Option<i32> {
// .first() devuelve Option<&i32>
let primero = lista.first()?;
// Si la lista estaba vacía, la línea anterior retornó None.
// Si llegamos aquí, 'primero' tiene el valor (desempaquetado).
Some(primero * 2)
}