当前位置: 首页 > news >正文

【Rust自学】9.4. 什么时候该使用panic!

喜欢的话别忘了点赞、收藏加关注哦,对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)

9.4.1. 总体原则

在9.1. 不可恢复的错误以及panic!中也讲了Rust中的错误类型有两种:可恢复的和不可恢复的。

调用panic!就相当于发生了一个不可恢复的错误。返回Result类型,这类错误就得到了传播,而且这类错误是可恢复的。

当你认为自己可以替代调用你代码的调用者来决定某些情况是不可恢复的时候,就可以写panic!

如果你写的函数返回的是Result,就相当于你把错误的处理权交给了代码的调用者,调用者就可以根据实际情况来决定是否要恢复这个错误,当然它也可以觉得这个错误是不可恢复的然后调用panic!来进行恐慌。

总而言之,如果你定义的是一个可能失败的函数,那么优先考虑返回Result类型,如果你认为某种情形是肯定不可恢复的,那就使用panic!

9.4.2.panic!适用的场景

编写示例,用于演示某些概念的时候可以使用panic!。在这类程序里面处理错误通常是使用unwrap这类可以制造恐慌的办法。在这里unwrap就相当于一个占位符,然后针对不同功能的不同错误再分别的编写代码进行个性化的处理。

在编写原型代码时可以使用panic!。因为在编写这类代码时还没想好该怎么处理错误, unwrapexpect方法在原型设计时非常方便,因为它们能制造恐慌,在代码中留下清晰的标记,后续就可以根据记号来对这些错误进行进一步的处理。

在编写测试代码时可以使用panic!。因为如果测试代码中的某个方法调用失败了,那么整个测试就应该被认定为失败,而失败状态正可以通过panic!来标记。

9.4.3. 你比编译器更了解情况

有时候你可以确定一个函数的调用返回的Result一定是Ok的,绝对不会出现恐慌,这个时候就可以使用unwrap。但是由于返回类似是Result,所以编译器仍然认为它可能出错,但你知道它一定不可能。

看个例子:

use std::net::IpAddr;
fn main(){let home: IpAddr = "127.0.0.1".parse().unwrap();
}

这个例子使用了IpAddr这个枚举,在main函数中写了"127.0.0.1",对它进行解析,我们知道"127.0.0.1"是一个合理的IP地址,返回值一定是Ok,所以后面就可以使用unwrap,它绝对不会出现恐慌。

9.4.5. 错误处理的指导性建议

当你的代码最终可能处于损坏状态(Bad State)时,最好使用panic!。损坏状态是指某些假设、保证、约定或不可变性被打破了。

比如说一些非法的值、矛盾的值或是空缺的值被传入代码。以及下列中的任意一条:

  • 这种损坏状态是一个意外
  • 在此之后的代码如果处于这种损坏状态就无法运行
  • 使用的类型中没有一个好方法来将这些处于损坏状态的信息进行编码

还是看一下具体的场景:

  • 传入了无意义的参数值:panic!
  • 调用外部不可控代码,返回非法状态,你又无法进行修复:panic!
  • 如果失败是可预期的(比如把字符串解析为数字):Result
  • 当你的代码对值进行操作,首先应该验证值的合法性,如果不合法:panic!
    这一点主要出于安全性的考虑,因为在尝试基于某些非法的值去进行操作的时候,就可能会暴露代码中的漏洞。这也是标准库会在代码尝试越界访问时报错的原因,因为尝试访问不属于当前数据结构的内存是一个普遍的安全问题。
    而且函数通常是有某种约定的,就是只有在输入数据满足某些特定条件下才能够正常运行,而在约定被违反时就应该出发恐慌。因为破坏这些约定往往预示着调用者端产生了bug,而因此产生的错误也不应该由调用者来进行解决,应该就地正法,出发恐慌。

9.4.6. 为验证创建自定义类型

以第二章讲的猜数游戏为例,有些代码不重要就跳过了:

fn main() {loop {// --snip--let guess: i32 = match guess.trim().parse() {Ok(num) => num,Err(_) => continue,};if guess < 1 || guess > 100 {println!("The secret number will be between 1 and 100.");continue;}match guess.cmp(&secret_number) {// --snip--}
}

这里对原本的代码进行了一些修改:

  • guess的值从u32改为i32,这样就能接收负数
  • 对于用户的输入是小于1大于100的情况,提醒用户神秘数字在1到100中间

如果字符串转整形解析失败,那么就会触发continue进行下一次迭代;如果数字的范围不在1到100之间,还会触发continue进行下一次迭代。针对这个小程序,可以把验证直接写在main函数里面,如果是一个大项目,每个函数都需要验证的话,那在每个函数里都写一遍验证逻辑就是相当麻烦的了。

针对这种情况,可以创建一个新的类型,把验证逻辑放到构造这个新类型实例的函数里就行,这样子只有通过验证的才能成功创建出实例,后续不需要担心所接收值的有效性。

看下例子:

pub struct Guess {value: i32,
}impl Guess {pub fn new(value: i32) -> Guess {if value < 1 || value > 100 {panic!("Guess value must be between 1 and 100, got {value}.");}Guess { value }}pub fn value(&self) -> i32 {self.value}
}fn main() {loop {// --snip--let guess: i32 = match guess.trim().parse() {Ok(num) => num,Err(_) => continue,};let guess = Guess::new(guess);match guess.value().cmp(&secret_number) {// --snip--}
}

new就是实例构造器,如果值不在1到100间就会panic!,如果没发生恐慌的话,那就创建一个Guess实例,value的值就是传入的值。

还定义了一个方法叫value,它会提取这个structvalue字段的值返回。

下面的main函数里就可以删掉验证值是否在1到100间的操作了,转而使用Guess::new这个构造器来验证。

如果要使用到guess的实际值,比如说match的时候,就可以使用value这个方法来获取。


http://www.mrgr.cn/news/82301.html

相关文章:

  • 【连续学习之LwM算法】2019年CVPR顶会论文:Learning without memorizing
  • 域上的多项式环,整除,相通,互质
  • PHP语言的计算机基础
  • vue3如何实现防抖?
  • 洛谷B4071 [GESP202412 五级] 武器强化
  • 医学图像分析工具01:FreeSurfer || Recon -all 全流程MRI皮质表面重建
  • 【Rust自学】9.3. Result枚举与可恢复的错误 Pt.2:传播错误、?运算符与链式调用
  • LeetCode算法题——螺旋矩阵ll
  • Prometheus之终极指南(The Ultimate Guide to Prometheus)
  • 题解:监控屏幕调整问题
  • UE蓝图战利品掉落动画
  • EFEVD: Enhanced Feature Extraction for Smart Contract Vulnerability Detection
  • 蓝桥杯JAVA--002
  • 基于深度学习的视觉检测小项目(五) 项目真正的开端
  • 前端如何用 canvas 做电影院选票功能
  • UE5动画蓝图
  • 24.Java 新特性扩展(重复注解、类型注解)
  • 人工智能安全与隐私——联邦遗忘学习(Federated Unlearning)
  • 51c视觉~合集40
  • 硬件设计-关于ADS54J60的校准问题
  • 多种方式访问mysql的对比分析
  • Pygame Zero(pgzrun)详解(简介、使用方法、坐标系、目录结构、语法参数、安装、实例解释)
  • NLP中的神经网络基础
  • SELECT的使用
  • GRAPE——RLAIF微调VLA模型:通过偏好对齐提升机器人策略的泛化能力(含24年具身模型汇总)
  • 矩阵的因子分解1-奇异值分解