De Option a Result en Rust: tutorial sin macros

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

¿Por qué te cansaste de escribir macros para Option en Result?

Escribes en Rust y de repente te das cuenta: tienes un Option<T>, pero necesitas un Result<T, E>. Parece una tontería. Pero si eres principiante, el primer pensamiento es: «¡Seguro que necesito un macro!» O ya eres un desarrollador experimentado, pero cada vez escribes lo mismo: match option { Some(v) => Ok(v), None => Err("error") }. Reconoce que es agotador.

La pregunta en Stack Overflow «Is there a way to simplify converting an Option into a Result without a macro?» ha acumulado miles de visitas. No es casualidad: el problema es familiar para todos los que trabajan con Rust. Pero la buena noticia es que hay una solución, y es sorprendentemente simple. En este tutorial, analizaremos todas las formas, desde las básicas hasta las elegantes, sin un solo macro.

No solo daremos la respuesta, sino que mostraremos cómo pensar como un desarrollador de Rust. ¡Vamos allá!



¿Qué son Option y Result en Rust?

Antes de convertir, asegurémonos de que estamos en la misma sintonía.

  • Option<T> — es un tipo que puede ser Some(T) (hay valor) o None (no hay valor). Se usa cuando la ausencia de valor es una situación normal (por ejemplo, buscar un elemento en una colección).
  • Result<T, E> — es un tipo que puede ser Ok(T) (todo bien) o Err(E) (ocurrió un error). Se usa para operaciones que pueden fallar (por ejemplo, leer un archivo).

El problema: a veces tienes un Option, pero según la lógica del programa, la ausencia de valor es un error. Ahí es donde se necesita la conversión.



Método básico: match (pero ya lo sabes)

La forma más obvia y usada por principiantes es match. Sí, funciona, pero es verbosa:

fn get_user_name(id: u32) -> Option<String> {    // ... alguna lógica    if id == 1 { Some("Alice".to_string()) } else { None }}

fn main() { let user_id = 2; let name = match get_user_name(user_id) { Some(name) => Ok(name), None => Err("User not found"), }; println!("{:?}", name); // Err("User not found")}

Este código es correcto, pero si tienes decenas de estas conversiones en el proyecto, te cansarás de escribir match. Además, es fácil equivocarse y olvidar manejar None. Necesitas algo más conciso.



Método ok_or: tu nuevo mejor amigo

La biblioteca estándar de Rust proporciona el método ok_or para Option. Hace exactamente lo que necesitamos: convierte Some(v) en Ok(v), y None en Err(error), donde error es el valor que pasas.

fn main() {    let user_id = 2;    let name = get_user_name(user_id).ok_or("User not found");    println!("{:?}", name); // Err("User not found")}

¡Solo una línea! Pero hay un detalle: ok_or toma un valor por propiedad (owned value). Si pasas un literal de cadena, como en el ejemplo, funciona porque &str implementa Into<String>. Pero si necesitas pasar un error que ya está almacenado en algún lugar, esto puede provocar una copia innecesaria.

Para estos casos existe ok_or_else — toma un closure que se ejecuta solo si el Option es None. Esto ahorra recursos:

fn main() {    let user_id = 2;    let error_msg = format!("User with id {} not found", user_id);    let name = get_user_name(user_id).ok_or_else(|| error_msg.clone());    // o incluso mejor:    let name = get_user_name(user_id).ok_or_else(|| format!("User {} not found", user_id));    println!("{:?}", name);}

Consejo: usa ok_or si el mensaje de error es una constante. Usa ok_

Blogs

Book Recommendations