Rust中panic的思考

今天在Rust相关的群里看到一个问题

A:怎么处理web服务中接口panic的?

我心想,Rust的强制错误处理在对比其他常用编程语言来说非常完善了,为啥会有panic呢?刚好就有人问出了我心中的这个问题。有如下对话:

B:你的接口还会 panic ? A:一般来说不会panic ,以防万一。比如,你之前开发的时候有bug ,上线了没查到,结果你接口panic了。这是很有可能的。举个例子,例如这种代码,开发期间正常编译,上线了绝对panic。

1
2
let mut v = vec![1, 2, 3];
v.remove(6);

B:所有根据索引操作的代码 和写 .unwrap() 没有区别呀。你会写 unwrap() 吗?

A:假设你就写了unwrap,但是没发现,要预防线上panic呀。你可以保证你自己不写panic ,人多了的情况下,很难保证所有人绝对不写panic

B:这样说好像不对, 真的代码质量这么差的话, 从提交代码的流程上就一定得有代码审核的过程。那要不然你还得防止 代码是不是有木马, 代码里有死循环, 代码里有任何东西都有可能

A:这是从管理上解决,问题是我们怎么从代码层面预防panic ,例如用了actix web的情况下

到这里我先去看了一下Vecremove操作源码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
pub fn remove(&mut self, index: usize) -> T {
    #[cold]
    #[inline(never)]
    fn assert_failed(index: usize, len: usize) -> ! {
        panic!("removal index (is {}) should be < len (is {})", index, len);
    }

    let len = self.len();
    if index >= len {
        assert_failed(index, len);
    }
    ... //后面操作部分省略
}

可以看到是因为一来就有一个assert_failed函数,对长度len进行了校验,如果失败,则直接panic

这里我首先想到的是Rust提供了捕获panic的方法catch_unwind,打开doc文档查看,发现好像是及其不推荐使用该方法的,而且还有可能捕获不到。

因为讨论的是web接口,于是我先去actix-web的库查找相关的issue,还真有一个: Panic within a handler should return an HTTP 500。该issue是在讨论发生panic时web框架应返回错误码500而不是直接就没有任何反应的断开连接。

在这个issue里,发起者提到了几种他认为的error级别的情况(例如数据库连接断开、数据库格式变化、上游服务器/API变化),他认为这些不应该一级一级向上传播,而是直接panic掉(比如调用pool.get().await.unwrap()从连接池获得连接时,如果得到了连接,那就ok,但是如果没有,这时无论如何做什么都会遭受灾难性的失败,因此需要panic,并且也提到了采用catch_unwind方法来捕获。

而库的现作者觉得,终止连接就是对panic最恰当的相应,用户在写编写业务代码的时候应该在所有情况下进行处理或冒泡(向上传递),并且catch_unwind也不等同于其他语言中的try-catch。

到这里,我其实是很认同库作者的发言的,我也觉得所有的错误都应该被处理掉,而不是当场产生panic,再去捕获它,或者说不应该使用panic做错误处理的流程。

所以就有了新的疑问,为啥 Rust 标准库里的 Vec 集合的 remove 方法会产生 panic ,还没有其他的方法也是类似的, 在日常开发中怎样能够避免出现会 panic 的代码?

于是去rust的github仓库查询相关issue,刚好就又有一个相关的:Add try_remove to Vec。这是一个pr,正如标题上所说,发起者添加了一个不会产生panictry_remove方法,该方法的返回值是Option<T>。照理说好像就没问题了,但是这个pr目前是关闭状态,继续往下看为什么。下面就有人回了,如果你要加这个,那就还应该增加try_的类似的方法,例如try_swap_removetry_inserttry_split_at,不然就会很奇怪。

然后下面就开始讨论是否有必要做这些事情,比如因为索引前提很简单,所以用户应该自己检查,又比如应该避免多次检查等,这里因为个人能力有限,所以建议大家去看原文。

虽然最后没能得到肯定的答案,我有如下一些想法:

  • 标准库要怎样设计能够既清晰明了,又能在不使用unsafe的情况下(不手动做unsafe的事),满足所有的需求,比如索引越界检查,虽然有讲到llvm能够优化部分检查操作,但这样是不是就又是一种隐式的经验问题了:我需要知道这样写,代码才性能好,因为这样才能让编译器给我优化。
  • 在工程中,能不能通过某些手段,去避免所有的会panic的代码。比如和panic相关的部分,交给最厉害的做,然后剩下的在提交代码做必须强制处理异常的检查。
updatedupdated2022-09-082022-09-08
加载评论