error handling - ¿Debería evitar desenvolverme en la aplicación de producción?
error-handling rust (4)
Es fácil colgar en tiempo de ejecución con unwrap
:
fn main() {
c().unwrap();
}
fn c() -> Option<i64> {
None
}
Resultado:
Compiling playground v0.0.1 (file:///playground)
Running `target/debug/playground`
thread ''main'' panicked at ''called `Option::unwrap()` on a `None` value'', ../src/libcore/option.rs:325
note: Run with `RUST_BACKTRACE=1` for a backtrace.
error: Process didn''t exit successfully: `target/debug/playground` (exit code: 101)
¿Está unwrap
solo diseñado para pruebas rápidas y pruebas de concepto?
No puedo afirmar "¡Mi programa no se bloqueará aquí, así que puedo usar unwrap
" si realmente quiero evitar el panic!
en tiempo de ejecución, ¡y creo que evito el panic!
es lo que queremos en una aplicación de producción.
En otras palabras, ¿puedo decir que mi programa es confiable si uso unwrap
? ¿O debo evitar unwrap
incluso si el caso parece simple?
Leí esta respuesta:
Se utiliza mejor cuando está seguro de que no tiene un error.
Pero no creo que pueda estar "seguro".
No creo que esta sea una pregunta de opinión, sino una pregunta sobre el núcleo y la programación de Rust.
En otras palabras, ¿puedo decir que mi programa es confiable si uso unwrap? ¿O debo evitar desenvolver incluso si el caso parece simple?
Creo que usar unwrap
juiciosamente es algo que debes aprender a manejar, no se puede evitar.
Mi interrogante retórico sería:
- ¿Puedo decir que mi programa es confiable si utilizo indexación en vectores, arrays o sectores?
- ¿Puedo decir que mi programa es confiable si utilizo la división de enteros?
- ¿Puedo decir que mi programa es confiable si agrego números?
(1) es como desenvolver, indexar pánico si comete una violación de contrato e intenta indexar fuera de límites. Esto sería un error en el programa, pero no capta tanta atención como una llamada para unwrap
.
(2) es como desplegar, pánico de división entera si el divisor es cero.
(3) a diferencia de desenvolver, además no verifica el desbordamiento en versiones de lanzamiento, por lo que puede dar como resultado silenciosos y errores lógicos.
Por supuesto, hay estrategias para manejar todo esto sin dejar casos de pánico en el código, pero muchos programas simplemente usan, por ejemplo, comprobación de límites tal como están.
Hay dos preguntas dobladas en una aquí:
- es el uso del
panic!
aceptable en producción - es el uso de
unwrap
aceptable en producción
panic!
es una herramienta que se utiliza, en Rust, para señalar situaciones irrecuperables / suposiciones violadas. Se puede usar para bloquear un programa que no puede continuar frente a esta falla (por ejemplo, situación OOM) o para solucionar el compilador sabiendo que no se puede ejecutar (por el momento).
unwrap
es una conveniencia, que es mejor evitar en producción. El problema de unwrap
es que no establece qué supuesto se violó, sino que es mejor usar expect("")
que es funcionalmente equivalente, pero también dará una pista de lo que salió mal (sin abrir el código fuente).
Si bien todo el tema "manejo de errores" es muy complicado ya menudo se basa en opiniones, esta pregunta se puede responder aquí, porque Rust tiene una filosofía bastante limitada. Es decir:
-
panic!
para errores de programación ("errores") - Propagación y manejo incorrectos de errores con
Result<T, E>
yOption<T>
para errores esperados y recuperables
Uno puede pensar en unwrap()
como una conversión entre esos dos tipos de errores (¡está convirtiendo un error recuperable en un panic!()
). Cuando escribes unwrap()
en tu programa, estás diciendo:
En este punto, un valor
None
/Err(_)
es un error de programación y el programa no puede recuperarse de él.
Por ejemplo, supongamos que está trabajando con un HashMap
y desea insertar un valor que puede querer mutar más adelante:
age_map.insert("peter", 21);
// ...
if /* some condition */ {
*age_map.get_mut("peter").unwrap() += 1;
}
Aquí usamos el unwrap()
, porque podemos estar seguros de que la clave contiene un valor. Sería un error de programación si no fuera así y aún más importante: no es realmente recuperable. ¿Qué harías cuando en ese momento no haya ningún valor con la tecla "peter"
? Intenta insertarlo de nuevo ...?
Pero como sabrá, hay una API de entry
hermosa para los mapas en la biblioteca estándar de Rust. Con esa API puedes evitar todos esos unwrap()
s. Y esto se aplica a casi todas las situaciones: muy a menudo puede reestructurar su código para evitar el unwrap()
! Solo en muy pocas situaciones no hay forma de evitarlo. Pero está bien usarlo, si quiere señalar: en este punto, sería un error de programación.
Ha habido una publicación de blog reciente y bastante popular sobre el tema de "manejo de errores" cuya conclusión es similar a la filosofía de Rust. Es bastante largo, pero vale la pena leerlo: "El modelo de error" . Aquí está mi intento de resumir el artículo en relación con esta pregunta:
- deliberadamente distinguir entre errores de programación y errores recuperables
- utilice un enfoque de "falla rápida" para la programación de errores
En resumen : use unwrap()
cuando esté seguro de que el error recuperable que obtiene es, de hecho, irrecuperable en ese punto. Puntos de bonificación por explicar "¿por qué?" En un comentario sobre la línea afectada ;-)
unwrap()
no es necesariamente peligroso. Al igual que con unreachable!()
Hay casos en los que puede estar seguro de que no se desencadenará alguna condición.
Las funciones que regresan La Option
o el Result
veces se ajustan a un rango más amplio de condiciones, pero debido a la estructura de su programa, es posible que esos casos nunca ocurran.
Por ejemplo: cuando crea un iterador a partir de un Vec
tor que construye usted mismo, conoce su longitud exacta y puede estar seguro de cuánto tiempo invocando next()
en él devuelve Some<T>
(y puede unwrap()
seguridad).