今天在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的情况下
到这里我先去看了一下Vec
的remove
操作源码:
|
|
可以看到是因为一来就有一个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,正如标题上所说,发起者添加了一个不会产生panic
的try_remove
方法,该方法的返回值是Option<T>
。照理说好像就没问题了,但是这个pr目前是关闭状态,继续往下看为什么。下面就有人回了,如果你要加这个,那就还应该增加try_
的类似的方法,例如try_swap_remove
,try_insert
,try_split_at
,不然就会很奇怪。
然后下面就开始讨论是否有必要做这些事情,比如因为索引前提很简单,所以用户应该自己检查,又比如应该避免多次检查等,这里因为个人能力有限,所以建议大家去看原文。
虽然最后没能得到肯定的答案,我有如下一些想法:
- 标准库要怎样设计能够既清晰明了,又能在不使用unsafe的情况下(不手动做unsafe的事),满足所有的需求,比如索引越界检查,虽然有讲到llvm能够优化部分检查操作,但这样是不是就又是一种隐式的经验问题了:我需要知道这样写,代码才性能好,因为这样才能让编译器给我优化。
- 在工程中,能不能通过某些手段,去避免所有的会panic的代码。比如和panic相关的部分,交给最厉害的做,然后剩下的在提交代码做必须强制处理异常的检查。