|
| 1 | +--- |
| 2 | +title: 'Operador de Propagación (`?`)' |
| 3 | +description: 'Mejorando el manejo de errores con el Operador de Propagación (`?`)' |
| 4 | +draft: true |
| 5 | +data: |
| 6 | + type: 'custom' |
| 7 | + topicLevel: 'start' |
| 8 | + position: |
| 9 | + x: 255 |
| 10 | + y: 520 |
| 11 | + width: 320 |
| 12 | + externalLinks: |
| 13 | + - name: 'Libro Oficial' |
| 14 | + english: false |
| 15 | + link: 'https://book.rustlang-es.org/ch06-01-defining-an-enum' |
| 16 | + - name: 'Documentacion Oficial' |
| 17 | + english: false |
| 18 | + link: 'https://google.github.io/comprehensive-rust/es/std-types/option.html' |
| 19 | + - name: 'Comprehensive Rust' |
| 20 | + english: true |
| 21 | + link: 'https://doc.rust-lang.org/std/option' |
| 22 | + - name: '¿Cómo almacena Rust los enum en memoria?' |
| 23 | + english: false |
| 24 | + link: 'https://blog.rustlang-es.org/articles/como-almacena-rust-los-enum-en-memoria' |
| 25 | +--- |
| 26 | +## Creando Tus Propios Errores en Rust: Buenas Prácticas y Herramientas |
| 27 | + |
| 28 | +El manejo de errores en Rust es fundamental para construir aplicaciones seguras y eficientes. Aunque `Result<T, E>` y `Option<T>` cubren la mayoría de los casos, cuando desarrollas bibliotecas o aplicaciones más complejas, es común que necesites crear tus propios errores personalizados. En este post, te guiaré por las mejores prácticas para crear tus propios errores en Rust, destacando el uso de enumeradores, las limitaciones de `Box<dyn Error>`, y cómo mejorar tu código con crates como `thiserror`, `anyhow` y `eyre`. |
| 29 | + |
| 30 | +### ¿Por qué crear tus propios errores? |
| 31 | + |
| 32 | +Rust fomenta el manejo explícito de errores mediante el uso de `Result` y `Option`. En muchos casos, el error que quieres propagar no está cubierto por los tipos estándar como `std::io::Error` o `std::fmt::Error`, por lo que crear tus propios tipos de error te permite: |
| 33 | + |
| 34 | +1. **Especificar el contexto exacto del fallo**. |
| 35 | +2. **Incluir variantes para distintos tipos de errores**. |
| 36 | +3. **Proporcionar mejores mensajes de error**. |
| 37 | +4. **Asegurarte de que el manejo de errores sea parte de la lógica del flujo de tu aplicación**. |
| 38 | + |
| 39 | +### Creando Errores con `enum` |
| 40 | + |
| 41 | +El enfoque recomendado para crear errores personalizados en Rust es usar un `enum`. Un `enum` puede representar diferentes variantes de error que pueden ocurrir en tu programa o biblioteca. Esto te permite manejar distintos tipos de errores de forma estructurada, con la ventaja de que el compilador te obliga a tratar todos los casos. |
| 42 | + |
| 43 | +Aquí tienes un ejemplo básico: |
| 44 | + |
| 45 | +```rust |
| 46 | +use std::fmt; |
| 47 | + |
| 48 | +#[derive(Debug)] |
| 49 | +enum MyError { |
| 50 | + NotFound, |
| 51 | + InvalidInput(String), |
| 52 | + IoError(std::io::Error), |
| 53 | +} |
| 54 | + |
| 55 | +impl fmt::Display for MyError { |
| 56 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 57 | + match self { |
| 58 | + MyError::NotFound => write!(f, "Recurso no encontrado"), |
| 59 | + MyError::InvalidInput(msg) => write!(f, "Entrada inválida: {}", msg), |
| 60 | + MyError::IoError(e) => write!(f, "Error de IO: {}", e), |
| 61 | + } |
| 62 | + } |
| 63 | +} |
| 64 | + |
| 65 | +impl std::error::Error for MyError {} |
| 66 | + |
| 67 | +fn main() -> Result<(), MyError> { |
| 68 | + // Simulamos un error |
| 69 | + Err(MyError::NotFound) |
| 70 | +} |
| 71 | +``` |
| 72 | + |
| 73 | +En este ejemplo: |
| 74 | + |
| 75 | +- `MyError` puede representar diferentes errores (`NotFound`, `InvalidInput` y `IoError`). |
| 76 | +- Implementamos `Display` para personalizar el mensaje de error y `std::error::Error` para integrarnos con el sistema de errores de Rust. |
| 77 | + |
| 78 | +### Por Qué Evitar `Box<dyn Error>` |
| 79 | + |
| 80 | +Una alternativa común es usar `Box<dyn std::error::Error>` para empaquetar errores. Esto permite la creación de errores dinámicos en tiempo de ejecución. Sin embargo, **esto no es recomendado**, ya que sacrifica el control estático que proporciona el sistema de tipos. Además, el uso de `Box<dyn Error>` tiene un coste en rendimiento, ya que introduce asignaciones en el heap y elimina la capacidad de optimización en tiempo de compilación. |
| 81 | + |
| 82 | +Por ejemplo: |
| 83 | + |
| 84 | +```rust |
| 85 | +fn error_con_box() -> Result<(), Box<dyn std::error::Error>> { |
| 86 | + let io_error = std::fs::File::open("archivo_inexistente.txt")?; |
| 87 | + Ok(()) |
| 88 | +} |
| 89 | +``` |
| 90 | + |
| 91 | +Aunque `Box<dyn Error>` es flexible, oculta la verdadera naturaleza de los errores que pueden ocurrir, lo que complica el manejo en el código llamador. |
| 92 | + |
| 93 | +### Mejorando el Manejo de Errores con `thiserror` |
| 94 | + |
| 95 | +Para evitar el tedio de implementar manualmente `Display`, `From`, y otras interfaces para tus errores, puedes usar el crate [`thiserror`](https://docs.rs/thiserror/latest/thiserror/). `thiserror` simplifica la creación de errores personalizados al generar automáticamente el código boilerplate, manteniendo tu código más limpio. |
| 96 | + |
| 97 | +Aquí tienes el mismo ejemplo anterior usando `thiserror`: |
| 98 | + |
| 99 | +```rust |
| 100 | +use thiserror::Error; |
| 101 | + |
| 102 | +#[derive(Error, Debug)] |
| 103 | +enum MyError { |
| 104 | + #[error("Recurso no encontrado")] |
| 105 | + NotFound, |
| 106 | + |
| 107 | + #[error("Entrada inválida: {0}")] |
| 108 | + InvalidInput(String), |
| 109 | + |
| 110 | + #[error(transparent)] |
| 111 | + IoError(#[from] std::io::Error), |
| 112 | +} |
| 113 | + |
| 114 | +fn main() -> Result<(), MyError> { |
| 115 | + // Simulamos un error |
| 116 | + Err(MyError::NotFound) |
| 117 | +} |
| 118 | +``` |
| 119 | + |
| 120 | +### Ventajas de `thiserror`: |
| 121 | + |
| 122 | +1. **Menos boilerplate:** No necesitas escribir manualmente implementaciones de `Display` o `From`. |
| 123 | +2. **Uso de atributos:** Puedes usar atributos para personalizar los mensajes de error y para manejar automáticamente la conversión de errores (como `#[from]`). |
| 124 | +3. **Mejor mantenimiento:** Al eliminar el código repetitivo, tu código se vuelve más legible y fácil de mantener. |
| 125 | + |
| 126 | +### Uso de `anyhow` y `eyre` para Errores Genéricos |
| 127 | + |
| 128 | +Si necesitas manejar errores de manera más flexible y no te importa tanto el tipo específico del error (por ejemplo, en aplicaciones CLI o proyectos rápidos), puedes usar el crate [`anyhow`](https://docs.rs/anyhow/latest/anyhow/) o su contraparte personalizable [`eyre`](https://docs.rs/color-eyre/latest/color-eyre/). Estos crates proporcionan tipos de error genéricos y facilitan el manejo de errores de una manera más sencilla, pero sin perder información útil. |
| 129 | + |
| 130 | +#### Ejemplo con `anyhow`: |
| 131 | + |
| 132 | +```rust |
| 133 | +use anyhow::{Result, Context}; |
| 134 | + |
| 135 | +fn funcion_con_error() -> Result<()> { |
| 136 | + let _file = std::fs::File::open("archivo.txt") |
| 137 | + .with_context(|| "No se pudo abrir el archivo")?; |
| 138 | + Ok(()) |
| 139 | +} |
| 140 | + |
| 141 | +fn main() -> Result<()> { |
| 142 | + funcion_con_error()?; |
| 143 | + Ok(()) |
| 144 | +} |
| 145 | +``` |
| 146 | + |
| 147 | +En este ejemplo: |
| 148 | + |
| 149 | +- `anyhow::Result` es un alias conveniente para `Result<T, anyhow::Error>`. |
| 150 | +- `with_context()` proporciona mensajes de error adicionales, lo que facilita el diagnóstico de fallos. |
| 151 | + |
| 152 | +#### Ejemplo con `eyre` y Errores Coloreados |
| 153 | + |
| 154 | +`eyre` permite un manejo de errores similar a `anyhow`, pero con la capacidad de agregar "reportes" de errores enriquecidos y con colores, ideales para la salida de errores en la terminal: |
| 155 | + |
| 156 | +```rust |
| 157 | +use color_eyre::eyre::Result; |
| 158 | + |
| 159 | +fn main() -> Result<()> { |
| 160 | + color_eyre::install()?; // Habilita los reportes coloreados |
| 161 | + |
| 162 | + let _file = std::fs::File::open("archivo.txt")?; |
| 163 | + Ok(()) |
| 164 | +} |
| 165 | +``` |
| 166 | + |
| 167 | +### Comparativa de Opciones |
| 168 | + |
| 169 | +| **Método** | **Ventajas** | **Desventajas** | |
| 170 | +|----------------------------|------------------------------------------------|--------------------------------------------| |
| 171 | +| **Enum manual** | Control preciso, tipado estático | Mucho código repetitivo | |
| 172 | +| **`Box<dyn Error>`** | Flexibilidad para manejar cualquier error | Oculta la naturaleza del error, más lento | |
| 173 | +| **`thiserror`** | Fácil de usar, sin boilerplate, mantiene tipos | Menos flexible que `Box<dyn Error>` | |
| 174 | +| **`anyhow`** | Simplicidad en el manejo de errores genéricos | Pierdes detalles sobre el tipo de error | |
| 175 | +| **`eyre`** | Reportes detallados y coloreados | Similar a `anyhow` en términos de flexibilidad | |
| 176 | + |
| 177 | +### Conclusión |
| 178 | + |
| 179 | +Crear tus propios errores en Rust es una parte crucial de escribir código seguro y robusto. Usar `enum` te proporciona el máximo control sobre el manejo de errores, mientras que herramientas como `thiserror` y `anyhow` te permiten reducir el código repetitivo y mejorar la legibilidad. Dependiendo del contexto de tu aplicación, puedes elegir entre un enfoque más tipado o uno más flexible. En cualquier caso, Rust proporciona las herramientas necesarias para garantizar que los errores sean manejados de manera segura y eficiente. |
0 commit comments