rust盖错了怎么拆除_细说Rust错误处理

细说Rust错误处理

1. 前言

这篇文章写得比较长,全文读完大约需要15-20min,如果对Rust的错误处理不清楚或还有些许模糊的同学,请静下心来细细阅读。当读完该篇文章后,可以说对Rust的错误处理可以做到掌握自如。

笔者花费较长篇幅来描述错误处理的来去,详细介绍其及一步步梳理内容,望大家能耐心读完后对大家有所帮助。当然,在写这篇文章之时,也借阅了大量互联网资料,详见链接见底部参考链接

掌握好Rust的错误设计,不仅可以提升我们对错误处理的认识,对代码结构、层次都有很大的帮助。那废话不多说,那我们开启这段阅读之旅吧 !

2. 背景

笔者在写这篇文章时,也翻阅一些资料关于Rust的错误处理资料,多数是对其一笔带过,导致之前接触过其他语言的新同学来说,上手处理Rust的错误会有当头棒喝的感觉。找些资料发现unwrap()也可以解决问题,然后心中暗自窃喜,程序在运行过程中,因为忽略检查或程序逻辑判断,导致某些情况,程序panic。这可能是我们最不愿看到的现象,遂又回到起点,重新去了解Rust的错误处理。

这篇文章,通过一步步介绍,让大家清晰知道Rust的错误处理的究竟。介绍在Rust中的错误使用及如何处理错误,以及在实际工作中关于其使用技巧。

3. unwrap的危害!

下面我们来看一段代码,执行一下:

fn main(){letpath="/tmp/dat";println!("{}",read_file(path));}fn read_file(path: &str)-> String {std::fs::read_to_string(path).unwrap()}

程序执行结果:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/libcore/result.rs:1188:5

stack backtrace:

0: backtrace::backtrace::libunwind::trace

at /Users/runner/.cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.40/src/backtrace/libunwind.rs:88

...

15: rust_sugar::read_file

at src/main.rs:7

16: rust_sugar::main

at src/main.rs:3

...

25: rust_sugar::read_file

note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

什么,因为path路径不对,程序竟然崩溃了,这个是我们不能接受的!

unwrap() 这个操作在rust代码中,应该看过很多这种代码,甚至此时我们正在使用它。它主要用于Option或Result的打开其包装的结果。常常我们在代码中,使用简单,或快速处理,使用了 unwrap() 的操作,但是,它是一个非常危险的信号!

可能因为没有程序检查或校验,潜在的bug可能就出现其中,使得我们程序往往就panic了。这可能使我们最不愿看到的现象。

在实际项目开发中,程序中可能充斥着大量代码,我们很难避免unwrap()的出现,为了解决这种问题,我们通过做code review,或使用脚本工具检查其降低其出现的可能性。

通常每个项目都有一些约束,或许:在大型项目开发中, 不用unwrap() 方法,使用其他方式处理程序,unwrap() 的不出现可能会使得程序的健壮性高出很多。

这里前提是团队或大型项目,如果只是写一个简单例子(demo)就不在本篇文章的讨论范畴。因为一个Demo的问题,可能只是快速示范或演示,不考虑程序健壮性, unwrap() 的操作可能会更方便代码表达。

可能有人会问,我们通常跑程序unit test,其中的很多mock数据会有 unwrap() 的操作,我们只是为了在单元测试中使得程序简单。这种也能不使用吗?答案:是的,完全可以不使用 unwrap() 也可以做到的。

4. 对比语言处理错误

说到unwrap(),我们不得不提到rust的错误处理,unwrap() 和Rust的错误处理是密不可分的。

4.1 golang的错误处理演示

如果了解golang的话,应该清楚下面这段代码的意思:

package main

import (

"io/ioutil"

"log"

)

func main() {

path := "/tmp/dat" //文件路径 file, err := readFile(path)

if err != nil {

log.Fatal(err) //错误打印 }

println("%s", file) //打印文件内容}

func readFile(path string) (string, error) {

dat, err := ioutil.ReadFile(path) //读取文件内容 if err != nil { //判断err是否为nil return "", err //不为nil,返回err结果 }

return string(dat), nil //err=nil,返回读取文件内容}

我们执行下程序,打印如下。执行错误,当然,因为我们给的文件路径不存在,程序报错。

2020/02/24 01:24:04 open /tmp/dat: no such file or directory

这里,golang采用多返回值方式,程序报错返回错误问题,通过判断 err!=nil 来决定程序是否继续执行或终止该逻辑。当然,如果接触过golang项目时,会发现程序中大量充斥着if err!=nil的代码,对此网上有对if err!=nil进行了很多讨论,因为这个不在本篇文章的范畴中,在此不对其追溯、讨论。

4.2 Rust 错误处理示例

对比了golang代码,我们对照上面的例子,看下在Rust中如何编写这段程序,代码如下:

fn main(){letpath="/tmp/dat";//文件路径matchread_file(path){//判断方法结果Ok(file)=>{println!("{}",file)}//OK 代表读取到文件内容,正确打印文件内容Err(e)=>{println!("{} {}",path,e)}//Err代表结果不存在,打印错误结果}}fn read_file(path: &str)-> Result{//Result作为结果返回值std::fs::read_to_string(path)//读取文件内容}

当前,因为我们给的文件路径不存在,程序报错,打印内容如下:

No such file or directory (os error 2)

在Rust代表中,Result是一个enum枚举对象,部分源码如下:

pubenum Result{/// Contains the success valueOk(#[stable(feature ="rust1", since ="1.0.0")]T),/// Contains the error valueErr(#[stable(feature ="rust1", since ="1.0.0")]E),}

通常我们使用Result的枚举对象作为程序的返回值,通过Result来判断其结果,我们使用match匹配的方式来获取Result的内容,判断正常(Ok)或错误(Err)。

或许,我们大致向上看去,golang代码和Rust代码没有本质区别,都是采用返回值方式,给出程序结果。下面我们就对比两种语言说说之间区别:golang采用多返回值方式,我们在拿到目标结果时(上面是指文件内容file),需要首先对err判断是否为nil,并且我们在return时,需要给多返回值分别赋值,调用时需要对 if err!=nil 做结果判断。

Rust中采用Result的枚举对象做结果返回。枚举的好处是:多选一。因为Result的枚举类型为Ok和Err,使得我们每次在返回Result的结果时,要么是Ok,要么是Err。它不需要return结果同时给两个值赋值,这样的情况只会存在一种可能性: Ok or Err 。

golang的函数调用需要对 if err!=nil做结果判断,因为这段代码 判断是手动逻辑,往往我们可能因为疏忽,导致这段逻辑缺失,缺少校验。当然,我们在编写代码期间可以通过某些工具 lint 扫描出这种潜在bug。

Rust的match判断是自动打开,当然你也可以选择忽略其中某一个枚举值,我们不在此说明。

可能有人发现,如果我有多个函数,需要多个函数的执行结果,这样需要match代码多次,代码会不会是一坨一坨,显得代码很臃肿,难看。是的,这个问题提出的的确是有这种问题,不过这个在后面我们讲解的时候,会通过程序语法糖避免多次match多次结果的问题,不过我们在此先不叙说,后面将有介绍。

5. Rust中的错误处理

前面不管是golang还是Rust采用return返回值方式,两者都是为了解决程序中错误处理的问题。好了,前面说了这么多,我们还是回归正题:Rust中是如何对错误进行处理的?

要想细致了解Rust的错误处理,我们需要了解std::error::Error,该trait的内部方法,部分代码如下: 参考链接:https://doc.rust-lang.org/std/error/trait.Error.html

pubtraitError: Debug+Display{fn description(&self)-> &str {"description() is deprecated; use Display"}#[rustc_deprecated(since ="1.33.0", reason ="replaced by Error::source, which can support \downcasting")]fn cause(&self)-> Option{self.source()}fn source(&self)-> Option{None}#[doc(hidden)]fn type_id(&self,_: private::Internal)-> TypeIdwhereSelf: 'static{TypeId::of::()}#[unstable(feature ="backtrace", issue ="53487")]fn backtrace(&self)-> Option{None}}description()在文档介绍中,尽管使用它不会导致编译警告,但新代码应该实现impl Display ,新impl的可以省略,不用实现该方法, 要获取字符串形式的错误描述,请使用to_string()。

cause()在1.33.0被抛弃,取而代之使用source()方法,新impl的不用实现该方法。

source()此错误的低级源,如果内部有错误类型Err返回:Some(e),如果没有返回:None。

如果当前Error是低级别的Error,并没有子Error,需要返回None。介于其本身默认有返回值None,可以不覆盖该方法。

如果当前Error包含子Error,需要返回子Error:Some(err),需要覆盖该方法。

type_id()该方法被隐藏。

backtrace()返回发生此错误的堆栈追溯,因为标记unstable,在Rust的stable版本不被使用。

自定义的Error需要impl std::fmt::Debug的trait,当然我们只需要在默认对象上添加注解:#[derive(Debug)]即可。

总结一下,自定义一个error需要实现如下几步:手动实现impl std::fmt::Display的trait,并实现 fmt(...)方法。

手动实现impl std::fmt::Debug的trait,一般直接添加注解即可:#[derive(Debug)]

手动实现impl std::error::Error的trait,并根据自身error级别是否覆盖std::error::Error中的source()方法。

下面我们自己手动实现下Rust的自定义错误:CustomError

usestd::error::Error;///自定义类型 Error,实现std::fmt::Debug的trait#[derive(Debug)]struct CustomError{err: ChildError,}///实现Display的trait,并实现fmt方法implstd::fmt::DisplayforCustomError{fn fmt(&self,f: &mutstd::fmt::Formatter)-> std::fmt::Result{write!(f,"CustomError is here!")}}///实现Error的trait,因为有子Error:ChildError,需要覆盖source()方法,返回Some(err)implstd::error::ErrorforCustomError{fn source(&self)-> Option{Some(&self.err)}}///子类型 Error,实现std::fmt::Debug的trait#[derive(Debug)]struct ChildError;///实现Display的trait,并实现fmt方法implstd::fmt::DisplayforChildError{fn fmt(&self,f: &mutstd::fmt::Formatter)-> std::fmt::Result{write!(f,"ChildError is here!")}}///实现Error的trait,因为没有子Error,不需要覆盖source()方法implstd::error::ErrorforChildError{}///构建一个Result的结果,返回自定义的error:CustomErrorfn get_super_error()-> Result{Err(CustomError{err: ChildError})}fn main(){matchget_super_error(){Err(e)=>{println!("Error: {}",e);println!("Caused by: {}",e.source().unwrap());}_=>println!("No error"),}}ChildError为子类型Error,没有覆盖source()方法,空实现了std::error::Error

CustomError有子类型ChildError,覆盖了source(),并返回了子类型Option值:Some(&self.err)

运行执行结果,显示如下:

Error: CustomError is here!

Caused by: ChildError is here!

至此,我们就了解了如何实现Rust中自定义Error了。

6. 自定义Error转换:From

上面我们说到,函数返回Result的结果时,需要获取函数的返回值是成功(Ok)还是失败(Err),需要使用match匹配,我们看下多函数之间调用是如何解决这类问题的?假设我们有个场景: 读取一文件 将文件内容转化为UTF8格式 * 将转换后格式内容转为u32的数字。

所以我们有了下面三个函数(省略部分代码):

...///读取文件内容fn read_file(path: &str)-> Result{std::fs::read_to_string(path)}/// 转换为utf8内容fn to_utf8(v: &[u8])-> Result{std::str::from_utf8(v)}/// 转化为u32数字fn to_u32(v: &str)-> Result{v.parse::()}

最终,我们得到u32的数字,对于该场景如何组织我们代码呢?unwrap()直接打开三个方法,取出值。这种方式太暴力,并且会有bug,造成程序panic,不被采纳。

match匹配,如何返回OK,继续下一步,否则报错终止逻辑,那我们试试。

参考代码如下:

fn main(){letpath="./dat";matchread_file(path){Ok(v)=>{matchto_utf8(v.as_bytes()){Ok(u)=>{matchto_u32(u){Ok(t)=>{println!("num:{:?}",u);}Err(e)=>{println!("{} {}",path,e)}}}Err(e)=>{println!("{} {}",path,e)}}}Err(e)=>{println!("{} {}",path,e)}}}///读取文件内容fn read_file(path: &str)-> Result{std::fs::read_to_string(path)}/// 转换为utf8内容fn to_utf8(v: &[u8])-> Result{std::str::from_utf8(v)}/// 转化为u32数字fn to_u32(v: &str)-> Result{v.parse::()}

天啊,虽然是实现了上面场景的需求,但是代码犹如叠罗汉,程序结构越来越深啊,这个是我们没法接受的!match匹配导致程序如此不堪一击。那么有没有第三种方法呢?当然是有的:From转换。

前面我们说到如何自定义的Error,如何我们将上面三个error收纳到我们自定义的Error中,将它们三个Error变成自定义Error的子Error,这样我们对外的Result统一返回自定义的Error。这样程序应该可以改变点什么,我们来试试吧。

#[derive(Debug)]enum CustomError{ParseIntError(std::num::ParseIntError),Utf8Error(std::str::Utf8Error),IoError(std::io::Error),}implstd::error::ErrorforCustomError{fn source(&self)-> Option{match&self{CustomError::IoError(refe)=>Some(e),CustomError::Utf8Error(refe)=>Some(e),CustomError::ParseIntError(refe)=>Some(e),}}}implDisplayforCustomError{fn fmt(&self,f: &mutFormatter)-> std::fmt::Result{match&self{CustomError::IoError(refe)=>e.fmt(f),CustomError::Utf8Error(refe)=>e.fmt(f),CustomError::ParseIntError(refe)=>e.fmt(f),}}}implFromforCustomError{fn from(s: std::num::ParseIntError)-> Self{CustomError::ParseIntError(s)}}implFromforCustomError{fn from(s: std::io::Error)-> Self{CustomError::IoError(s)}}implFromforCustomError{fn from(s: std::str::Utf8Error)-> Self{CustomError::Utf8Error(s)}}CustomError为我们实现的自定义Error

CustomError有三个子类型Error

CustomError分别实现了三个子类型Error From的trait,将其类型包装为自定义Error的子类型

好了,有了自定义的CustomError,那怎么使用呢? 我们看代码:

usestd::io::ErrorasIoError;usestd::str::Utf8Error;usestd::num::ParseIntError;usestd::fmt::{Display,Formatter};fn main()-> std::result::Result{letpath="./dat";letv=read_file(path)?;letx=to_utf8(v.as_bytes())?;letu=to_u32(x)?;println!("num:{:?}",u);Ok(())}///读取文件内容fn read_file(path: &str)-> std::result::Result{std::fs::read_to_string(path)}/// 转换为utf8内容fn to_utf8(v: &[u8])-> std::result::Result{std::str::from_utf8(v)}/// 转化为u32数字fn to_u32(v: &str)-> std::result::Result{v.parse::()}#[derive(Debug)]enum CustomError{ParseIntError(std::num::ParseIntError),Utf8Error(std::str::Utf8Error),IoError(std::io::Error),}implstd::error::ErrorforCustomError{fn source(&self)-> Option{match&self{CustomError::IoError(refe)=>Some(e),CustomError::Utf8Error(refe)=>Some(e),CustomError::ParseIntError(refe)=>Some(e),}}}implDisplayforCustomError{fn fmt(&self,f: &mutFormatter)-> std::fmt::Result{match&self{CustomError::IoError(refe)=>e.fmt(f),CustomError::Utf8Error(refe)=>e.fmt(f),CustomError::ParseIntError(refe)=>e.fmt(f),}}}implFromforCustomError{fn from(s: std::num::ParseIntError)-> Self{CustomError::ParseIntError(s)}}implFromforCustomError{fn from(s: std::io::Error)-> Self{CustomError::IoError(s)}}implFromforCustomError{fn from(s: std::str::Utf8Error)-> Self{CustomError::Utf8Error(s)}}

其实我们主要关心的是这段代码:

fn main()-> Result{letpath="./dat";letv=read_file(path)?;letx=to_utf8(v.as_bytes())?;letu=to_u32(x)?;println!("num:{:?}",u);Ok(())}

我们使用了?来替代原来的match匹配的方式。?使用问号作用在函数的结束,意思是:程序接受了一个Result自定义的错误类型。

当前如果函数结果错误,程序自动抛出Err自身错误类型,并包含相关自己类型错误信息,因为我们做了From转换的操作,该函数的自身类型错误会通过实现的From操作自动转化为CustomError的自定义类型错误。

当前如果函数结果正确,继续之后逻辑,直到程序结束。

这样,我们通过From和?解决了之前match匹配代码层级深的问题,因为这种转换是无感知的,使得我们在处理好错误类型后,只需要关心我们的目标值即可,这样不需要显示对Err(e)的数据单独处理,使得我们在函数后添加?后,程序一切都是自动了。

还记得我们之前讨论在对比golang的错误处理时的:if err!=nil的逻辑了吗,这种因为用了?语法糖使得该段判断将不再存在。

另外,我们还注意到,Result的结果可以作用在main函数上,是的,Result的结果不仅能作用在main函数上

Result还可以作用在单元测试上,这就是我们文中刚开始提到的:因为有了Result的作用,使得我们在程序中几乎可以完全摒弃unwrap()的代码块,使得程序更轻,大大减少潜在问题,程序组织结构更加清晰。

下面这是作用在单元测试上的Result的代码:

...#[cfg(test)]mod tests{usesuper::*;#[test]fn test_get_num()-> std::result::Result{letpath="./dat";letv=read_file(path)?;letx=to_utf8(v.as_bytes())?;letu=to_u32(x)?;assert_eq!(u,8);Ok(())}}

7. 重命名Result

我们在实际项目中,会大量使用如上的Result结果,并且Result的Err类型是我们自定义错误,导致我们写程序时会显得非常啰嗦、冗余

///读取文件内容fn read_file(path: &str)-> std::result::Result{letval=std::fs::read_to_string(path)?;Ok(val)}/// 转换为utf8内容fn to_utf8(v: &[u8])-> std::result::Result{letx=std::str::from_utf8(v)?;Ok(x)}/// 转化为u32数字fn to_u32(v: &str)-> std::result::Result{leti=v.parse::()?;Ok(i)}

我们的程序中,会大量充斥着这种模板代码,Rust本身支持对类型自定义,使得我们只需要重命名Result即可:

pubtype IResult=std::result::Result;///自定义Result类型:IResult

这样,凡是使用的是自定义类型错误的Result都可以使用IResult来替换std::result::Result的类型,使得简化程序,隐藏Error类型及细节,关注目标主体,代码如下:

///读取文件内容fn read_file(path: &str)-> IResult{letval=std::fs::read_to_string(path)?;Ok(val)}/// 转换为utf8内容fn to_utf8(v: &[u8])-> IResult{letx=std::str::from_utf8(v)?;Ok(x)}/// 转化为u32数字fn to_u32(v: &str)-> IResult{leti=v.parse::()?;Ok(i)}

将std::result::Result 替换为:IResult类型

当然,会有人提问,如果是多参数类型怎么处理呢,同样,我们只需将OK类型变成 tuple (I,O)类型的多参数数据即可,大概这样:

pubtype IResult=std::result::Result;

使用也及其简单,只需要返回:I,O的具体类型,举个示例:

fn foo()-> IResult{Ok((String::from("bar"),32))}

使用重命名类型的Result,使得我们错误类型统一,方便处理。在实际项目中,可以大量看到这种例子的存在。

8. Option转换

我们知道,在Rust中,需要使用到unwrap()的方法的对象有Result,Option对象。我们看下Option的大致结构:

pubenum Option{/// No value#[stable(feature ="rust1", since ="1.0.0")]None,/// Some value `T`#[stable(feature ="rust1", since ="1.0.0")]Some(#[stable(feature ="rust1", since ="1.0.0")]T),}

Option本身是一个enum对象,如果该函数(方法)调用结果值没有值,返回None,反之有值返回Some(T)

如果我们想获取Some(T)中的T,最直接的方式是:unwrap()。我们前面说过,使用unwrap()的方式太过于暴力,如果出错,程序直接panic,这是我们最不愿意看到的结果。

Ok,那么我们试想下, 利用Option能使用?语法糖吗?如果能用?转换的话,是不是代码结构就更简单了呢?我们尝试下,代码如下:

#[derive(Debug)]enum Error{OptionError(String),}implstd::error::ErrorforError{}implstd::fmt::DisplayforError{fn fmt(&self,f: &mutstd::fmt::Formatter)-> std::fmt::Result{match&self{Error::OptionError(refe)=>e.fmt(f),}}}pubtype Result=std::result::Result;fn main()-> Result{letbar=foo(60)?;assert_eq!("bar",bar);Ok(())}fn foo(index: i32)-> Option{ifindex>60{returnSome("bar".to_string());}None}

执行结果报错:

error[E0277]: `?` couldn't convert the error to `Error`

--> src/main.rs:22:22

|

22 | let bar = foo(60)?;

| ^ the trait `std::convert::From<:option::noneerror>` is not implemented for `Error`

|

= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait

= note: required by `std::convert::From::from`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.

error: could not compile `hyper-define`.

提示告诉我们没有转换std::convert::From<:option::noneerror>,但是NoneError本身是unstable,这样我们没法通过From转换为自定义Error。

本身,在Rust的设计中,关于Option和Result就是一对孪生兄弟一样的存在,Option的存在可以忽略异常的细节,直接关注目标主体。当然,Option也可以通过内置的组合器ok_or()方法将其变成Result。我们大致看下实现细节:

implOption{pubfn ok_or(self,err: E)-> Result{matchself{Some(v)=>Ok(v),None=>Err(err),}}}

这里通过ok_or()方法通过接收一个自定义Error类型,将一个Option->Result。好的,变成Result的类型,我们就是我们熟悉的领域了,这样处理起来就很灵活。

关于Option的其他处理方式,不在此展开解决,详细的可看下面链接:

9. 避免unwrap()

有人肯定会有疑问,如果需要判断的逻辑,又不用?这种操作,怎么取出Option或Result的数据呢,当然点子总比办法多,我们来看下Option如何做的:

fn main(){ifletSome(v)=opt_val(60){println!("{}",v);}}fn opt_val(num: i32)-> Option{ifnum>=60{returnSome("foo bar".to_string());}None}

是的,我们使用if let Some(v)的方式取出值,当前else的逻辑就可能需要自己处理了。当然,Option可以这样做,Result也一定可以:

fn main(){ifletOk(v)=read_file("./dat"){println!("{}",v);}}fn read_file(path: &str)-> Result{std::fs::read_to_string(path)}

只不过,在处理Result的判断时,使用的是if let Ok(v),这个和Option的if let Some(v)有所不同。

到这里,unwrap()的代码片在项目中应该可以规避了。补充下,这里强调了几次规避,就如前所言:团队风格统一,方便管理代码,消除潜在危机。

10. 自定义Error同级转换

我们在项目中,一个函数(方法)内部会有多次Result的结果判断:?,假设我们自定义的全局Error名称为:GlobalError。

这时候,如果全局有一个Error可能就会出现如下错误:

std::convert::From<:globalerror>>`isnotimplementedfor`error::GlobalError

意思是:我们自定义的GlobalError没有通过From>转换我们自己自定义的GlobalError,那这样,就等于自己转换自己。注意:第一:这是我们不期望这样做的。

第二:遇到这种自己转换自己的T类型很多,我们不可能把出现的T类型通通实现一遍。 这时候,我们考虑自定义另一个Error了,假设我们视为:InnnerError,我们全局的Error取名为:GlobalError,我们在遇到上面错误时,返回Result,这样我们遇到Result时,只需要通过From转换即可,代码示例如下:

implFromforGlobalError{fn from(s: InnerError)-> Self{Error::new(ErrorKind::InnerError(e))}}

上面说的这种情况,可能会在项目中出现多个自定义Error,出现这种情况时,存在多个不同Error的std::result::Result的返回。这里的Err就可以根据我们业务现状分别反回不同类型了。最终,只要实现了From的trait可转化为最终期望结果。

11. Error常见开源库

好了,介绍到这里,我们应该有了非常清晰的认知:关于如何处理Rust的错误处理问题了。但是想想上面的这些逻辑多数是模板代码,我们在实际中,大可不必这样。说到这里,开源社区也有了很多对错误处理库的支持,下面列举了一些:

12. 参考链接

13 错误处理实战

这个例子介绍了如何在https://github.com/Geal/nom中处理错误,这里就不展开介绍了,有兴趣的可自行阅读代码。

14. 总结

好了,经过上面的长篇大论,不知道大家是否明白如何自定义处理Error呢了。大家现在带着之前的已有的问题或困惑,赶紧实战下Rust的错误处理吧,大家有疑问或者问题都可以留言我,希望这篇文章对你有帮助。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/521302.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

机器学习应用中的UI个性化

摘要&#xff1a; 在这篇文章中&#xff0c;我们看看关于机器学习应用中的UI个性化问题&#xff0c;谈一谈为什么在应用程序开发的过程中交流和沟通是成功的关键。 EdgVerve推出了基于AI的业务应用平台的新一代集成人工智能平台-Infosys Nia使你的企业能够管理特定的业务领域&a…

zookeeper集群部署 精简版本

文章目录1. zookeeper下载2. 解压3. 重命名4. 创建dataDir目录和dataLogDir日志目录5. 更新备份配置文件6. 添加数据目录和日志文件目录7. 添加集群信息7.1. ip配置 方式017.2. 域名配置 方式02&#xff08;推荐使用&#xff09;8. 在服务器上分别创建myid&#xff0c;各自写入…

物联网火爆,入门却太难了!

近几年来&#xff0c;物联网发展迅速&#xff1a;据中商产业研究院《2016——2021年中国物联网产业市场研究报告》显示&#xff0c;预计到2020年&#xff0c;中国物联网的整体规模将达2.2万亿元&#xff0c;产业规模比互联网大30倍。我们可以看到&#xff0c;物联网的前景广阔。…

精读《手写 SQL 编译器 - 回溯》

摘要&#xff1a; 1 引言 上回 精读《手写 SQL 编译器 - 语法分析》 说到了如何利用 Js 函数实现语法分析时&#xff0c;留下了一个回溯问题&#xff0c;也就是存档、读档问题。 我们把语法分析树当作一个迷宫&#xff0c;有直线有岔路&#xff0c;而想要走出迷宫&#xff0c;在…

感知器算法的基本原理和步骤_很多情况下,深度学习算法和人脑相似

人脑模拟  深度学习背后的主要原因是人工智能应该从人脑中汲取灵感。此观点引出了“神经网络”这一术语。人 脑中 包含 数 十亿个神经元&#xff0c;它 们 之间有 数 万个 连 接。很多情况下&#xff0c;深度学习算法和人脑相似&#xff0c;因为人脑和深度学习模型都拥有大量…

打印时候复选框勾选不见了_checkbox 选中未显示对号勾选的问题

今天同事 让帮忙调试一个checkbox只选中一个的方法&#xff0c;代码如下&#xff1a;ID平台代码平台名称选项2选项2选项3选项4varEleInput$("[name ptcode]:checkbox")//;$("#table_platform input") ;EleInput.each(function(index, element) {$(element…

工程师如何解决穿衣搭配烦恼?——滴搭平台与算法

摘要&#xff1a; 阿里工程师们推出了一个滴搭平台&#xff0c;基于千万时尚达人的优质搭配&#xff0c;已经学习出了一套比较成熟的算法&#xff0c;帮你找到最合适的穿搭。不信&#xff1f;下面一起来深入了解“滴搭”背后的算法。 作为一名工程师&#xff0c;每天与代码打交…

Linux下搭建 kafka集群 + zookeeper集群部署 安装、启动、停止

文章目录一、环境部署总览1. 软件版本选型2. 服务器软件部署总览二、软件部署手册2.1. JDK2.2. kafka2.3. Kafka Eagle2.4. mysql2.5. zookeeper2.6. maven三、kafka集群部署3.1. 启动zk集群3.2. 启动kafka集群3.3. 启动Kafka Eagle一、环境部署总览 1. 软件版本选型 软件版本…

刷爆了!GitHub标星1.6W,这个 Python 项目太实用!

GitHub上&#xff0c;一份用Python开发的12306 购票助手火了&#xff01;这个用 Python 开发的 12306 购票助手&#xff0c;已经有 1.6w star&#xff0c;作者也一直在维护。它实现了自动打码&#xff0c;自动登录&#xff0c;捡漏&#xff0c;候补等功能。用 Python 帮你抢票&…

flexcell控件 许可证信息没有找到_报表控件 ActiveReports 全面迎来 .Net Core 时代

报表控件ActiveReports全面迎来 .Net Core 时代&#xff01;一键创建 .Net Core MVC 项目近期&#xff0c;葡萄城报表控件ActiveReports V14.0 正式发布&#xff0c;全面支持 .NET Core平台。同时&#xff0c;在本次更新中 ActiveReports的桌面报表设计器UI得以全面增强&#x…

彻底卸载acer软件保护卡_宏碁(Acer)传奇 14英寸 新一代7nm六核处理器 真香机 高性能宏基笔记本电脑(R5-4500U 7纳米 16G 512GSSD )...

拜托朋友帮忙推荐的一款4k左右笔记本&#xff0c;要求:简单办公学习&#xff0c;看视频娱乐即可。然后朋友帮忙推荐了两款。一款是荣耀magic。另一款就是这款宏碁传奇预售款。京东标题如下:【宏碁&#xff08;Acer&#xff09;传奇 14英寸 新一代7nm六核处理器 真香机 高性能 轻…

图解集成学习中的梯度提升思想

摘要&#xff1a; 本文讲述集成学习中的梯度提升方法的思想&#xff0c;以简单算术及图片的形式展示整个过程&#xff0c;一看就懂&#xff01; 简介 机器学习&#xff08;ML&#xff09;中的一个关键步骤是选择适合数据的最佳算法&#xff0c;根据数据中的一些统计数据和可视…

AI助手智商测评Siri进步最大,无人驾驶打车服务已在美国试行

摘要&#xff1a; 万年老幺Google Assistant稳坐AI助手第一的位置&#xff0c;siri屈居老二可进步倒是不小&#xff01;无人驾驶打车已经成为现实&#xff0c;就问你敢坐不敢坐&#xff01;最接地气儿的CEO马斯克&#xff0c;变身快递员送货到家只为卖车......支付宝小程序也来…

Kafka 监控 Kafka Eagle 精简版本

文章目录一、Kafka Eagle 下载、编译流程1. Kafka Eagle下载2. 解压Kafka Eagle3. 进入解压的目录4. 编译项目5. 添加编译环境6. 运行脚本编译项目二、Kafka Eagle 正式配置流程2.1. 进入编译获得web目录2.2. 将编译后的tar解压到/app目录2.3. 在/app目录下面查看2.4. 配置文件…

arcgis python实例_ArcGIS Python编程案例(14)-五个常用Python处理任务

我们将在本章介绍以下案例&#xff1a; 从分隔符文本文件中读取数据 发送电子邮件 访问FTP服务器中的文件 创建ZIP文件 读取XML文件 引言 在本章中&#xff0c;你将学习如何编写Python脚本来执行常用的处理任务。这些任务包括读写分隔符文本文件&#xff0c;发送电子邮件&#…

c 最大子序列和_最大连续子序列

最大连续子序列&#xff1a;是指序列中所有子序列中元素和最大的一个例如{-2,11&#xff0c;-4,13&#xff0c;-5.-2}中最大连续子序列为{11&#xff0c;-4,13}其和为20若所有k个元素都为负数&#xff0c;则定义其最大和为0&#xff0c;输出整个序列的首尾元素#include<iost…

【只有光头才能变强,文末有xx】分享一波Lambda表达式

戳蓝字“CSDN云计算”关注我们哦&#xff01;作者 | Java3y责编 | 阿秃前言只有光头才能变强。学了一下Java的函数式编程&#xff0c;给大家整理了一下&#xff0c;一起学习&#xff01;一、Lambda用法之前写Optional这个类的时候&#xff0c;简单说了一下Lambda是怎么用的&am…

因为阿里,他们成了“杭漂”

摘要&#xff1a; 这是你吗——有人在回北京的飞机上赶时间写周报&#xff1b;有人全家在美国&#xff0c;孤身从硅谷回杭州工作&#xff1b;有人每周回北京为了赶末班飞机快速过安检&#xff0c;周末从不系皮带。 阿里正在从杭州走向全世界&#xff0c;也有越来越多的人才&am…

如何提高一个研发团队的“代码速度”?

摘要&#xff1a; 蚂蚁金服国际事业群技术风险部研究员南门&#xff0c;将和大家聊聊Code Velocity&#xff0c;希望能在团队效率问题方面&#xff0c;为你带来一些启发。 什么是代码速度&#xff08;Code Velocity&#xff09;&#xff1f; Code Velocity的定义是&#xff1…

多机器人路径规划的代码_知荐 | 地平线机器人算法工程师总结六大路径规划算法...

来源 | 知乎知圈 | 进“高精度地图社群”&#xff0c;请加微信15221054164&#xff0c;备注地图目录1 自主机器人近距离操作运动规划体系1.1 单个自主机器人的规划体系1.2 多自主机器人协同规划体系2 路径规划研究2.1 图搜索法2.1.1 可视图法2.1.2 Dijkstra算法2.1.3 A*算法2.2…