这篇文章主要介绍“Rust错误处理有哪些”,在日常操作中,相信很多人在Rust错误处理有哪些问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Rust错误处理有哪些”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
西华网站建设公司成都创新互联,西华网站设计制作,有大型网站制作公司丰富经验。已为西华成百上千家提供企业网站建设服务。企业网站搭建\外贸营销网站建设要多少钱,请找那个售后服务好的西华做网站的公司定做!

错误处理是编程语言中很重要的一个方面。目前,错误处理的方式分为两类,第一类是以C语言为首的基于返回值的错误处理方案,第二类是以Java语言为首的基于异常的错误处理方案。也可以从发生了错误是否可恢复来进行分类,例如,C语言中对可恢复的错误会使用错误码返回值,对不可恢复的错误会直接调用exit来退出程序;Java的异常体系分为Exception和Error,分别对应可恢复错误和不可恢复错误。在Rust中,错误处理的方案和C语言类似,但更加完善好用:对于不可恢复错误,使用panic来处理,使得程序直接退出并可输出相关信息;对于可恢复错误,使用Option和Result来对返回值进行封装,表达能力更强。
不可恢复错误
panic简介
对于不可恢复错误,Rust提供了panic机制来使得程序迅速崩溃,并报告相应的出错信息。panic出现的场景一般是:如果继续执行下去就会有极其严重的内存安全问题,这种时候让程序继续执行导致的危害比崩溃更严重。举个例子:
fn main() {
let v = vec![1, 2, 3];
println!("{:?}", v[6]);
}对于上面的程序,数组v有三个元素,但索引值是6,所以运行后程序会崩溃并报以下错误:
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 6', src/main.rs:176:22 stack backtrace: 函数调用栈...
panic实现机制
在Rust中,panic的实现机制有两种方式:
unwind方式:发生panic时,会一层一层地退出函数调用栈,栈内的局部变量还可以正常析构。
abort方式:发生panic时,直接退出整个程序。
默认情况下编译器使用unwind方式,函数调用栈信息可以帮助我们快速定位发生panic的第一现场;但某些嵌入式系统因资源不足而只能选择abort方式,可以通过rustc -C panic=abort test.rs方式指定。
在Rust中,通过unwind方式实现的panic,其内部实现方式基本与C++的异常是一样的。Rust提供了一些工具函数,可以像try-catch机制那样让用户在代码中终止栈展开,例如:
fn main() {
std::panic::catch_unwind(|| {
let v = vec![1, 2, 3];
println!("{:?}", v[6]);
println!("interrupted"); // 没有输出
})
.ok();
println!("continue"); // 正常输出
}运行程序可以发现,println!("interrupted");语句没有执行,因此在上一条语句出发了panic,这个函数调用栈开始销毁,但std::panic::catch_unwind阻止了调用栈的继续展开,因此println!("continue");得以正常执行。
需要注意的是,不要像try-catch那样使用catch_unwind来进行流程控制,Rust更推荐基于返回值的错误处理机制,因为既然发生panic了,就让程序越早崩溃越好,这有利于调试bug,而使用catch_unwind会让错误暂时被压制,从而让错误传递到其他位置,导致不容易找到程序崩溃的第一现场。catch_unwind主要用于以下两种情况:
在FFI的场景下,若C语言调用了Rust的函数,在Rust内部出现了panic,如果这个panic在Rust内部没处理好,直接扔到C代码中去,会导致产生“未定义行为”。
某些高级抽象机制需要阻止栈展开,例如线程池。如果一个线程中出现了panic,我们只希望把这个线程关闭,而不是将整个线程池拖下水。
可恢复错误
基本错误处理
对于可恢复的错误,Rust中提供了基于返回值的方案,主要基于Option和Result类型。Option代表返回值要么是空要么是非空,Result代表返回值要么是正常值的要么错误值。它们的定义如下:
pub enum Option{ /// No value None, /// Some value `T` Some(#[stable(feature = "rust1", since = "1.0.0")] T), } pub enum Result { /// Contains the success value Ok(#[stable(feature = "rust1", since = "1.0.0")] T), /// Contains the error value Err(#[stable(feature = "rust1", since = "1.0.0")] E), }
我们来看一个标准库中对Result的典型用法,FromStr中的from_str方法可以通过字符串构造出当前类型的实例,但可能会构造失败。标准库中针对bool类型实现了这个trait,正常情况返回bool类型的值,异常情况返回ParseBoolError类型的值:
pub trait FromStr: Sized {
/// The associated error which can be returned from parsing.
type Err;
fn from_str(s: &str) -> Result;
}
impl FromStr for bool {
type Err = ParseBoolError;
fn from_str(s: &str) -> Result {
match s {
"true" => Ok(true),
"false" => Ok(false),
_ => Err(ParseBoolError { _priv: () }),
}
}
} 我们再来看一个标准库中对Option的典型用法,Iterator的next方法要么返回下一个元素,要么无元素可返回,因此使用Option非常合适。
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub trait Iterator {
type Item;
fn next(&mut self) -> Option;
...
} Option类型解决了许多编程语言中存在的空指针问题。空指针这个设计在加入编程语言时没有经过深思熟虑,而只是因为易于实现而已。空指针最大的问题在于,它违背了类型系统的规定。类型规定了数据可能的取值范围,规定了在这些值上可能的操作,也规定了这些数据代表的含义,还规定了这些数据的存储方式。但是,一个普通的指针和一个空指针,哪怕它们是同样的类型,做同样的操作,所得到的结果是不同的。因此,并不能说空指针和普通指针是同一个类型,空指针在类型系统上打开了一个缺口,引入了一个必须在运行期特殊处理的值,它让编译器的类型检查在此失去了意义。对此,Rust的解决方案是把空指针null从一个值上升为一个类型,用enum类型的Option的None来代表空指针,而Rust中的enum要求在使用时必须对enum的每一种可能性都进行处理,因此强迫程序员必须考虑到Option为None的情形。C/C++中也增添了类似的设计,但由于前向兼容的问题,无法强制使用,因此其作用也就弱化了很多。
问号运算符
Rust中提供了问号运算符?语法糖来简化Result和Option的使用,问号运算符的意思是,如果结果是Err,则提前返回,否则继续执行。?对应着std::ops::Try这个trait,编译器会把expr?这个表达式自动转换为以下语义:
match Try::into_result(expr) {
Ok(V) => v,
Err(e) => return Try::from_error(From::from(e)),
}标准库中已经为Result和Option两个类型实现了Try:
implops::Try for Option { type Ok = T; type Error = NoneError; fn into_result(self) -> Result { self.ok_or(NoneError) } fn from_ok(v: T) -> Self { Some(v) } fn from_error(_: NoneError) -> Self { None } } impl ops::Try for Option { type Ok = T; type Error = NoneError; fn into_result(self) -> Result { self.ok_or(NoneError) } fn from_ok(v: T) -> Self { Some(v) } fn from_error(_: NoneError) -> Self { None } }
可以看到,对于Result类型,执行问号运算符时,如果碰到Err,则调用Fromtrait做类型转换,然后中断当前逻辑提前返回。
需要注意的是,问号运算符的引入给main函数带来了挑战,因为问号运算符要求函数返回值是Result类型,而main函数是fn() -> ()类型,解决这个问题的办法就是修改main函数的签名类型,但这样又会破坏旧代码。Rust最终的解决方案是引入了一个trait:
pub trait Termination {
/// Is called to get the representation of the value as status code.
/// This status code is returned to the operating system.
fn report(self) -> i32;
}
impl Termination for () {
#[inline]
fn report(self) -> i32 {
ExitCode::SUCCESS.report()
}
}
impl Termination for Result<(), E> {
fn report(self) -> i32 {
match self {
Ok(()) => ().report(),
Err(err) => Err::(err).report(),
}
}
}
impl Termination for ! {
fn report(self) -> i32 {
self
}
}
impl Termination for Result {
fn report(self) -> i32 {
let Err(err) = self;
eprintln!("Error: {:?}", err);
ExitCode::FAILURE.report()
}
}
impl Termination for ExitCode {
#[inline]
fn report(self) -> i32 {
self.0.as_i32()
}
} main函数的签名就对应地改成了fn,标准库为Result类型、()类型等都实现了这个trait,从而这些类型都可以作为main函数的返回类型了。
到此,关于“Rust错误处理有哪些”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注创新互联网站,小编会继续努力为大家带来更多实用的文章!
当前题目:Rust错误处理有哪些
分享链接:http://www.jxjierui.cn/article/gejope.html


咨询
建站咨询
