├── dat ├── handle_error.png ├── time_tpl.file ├── Cargo.toml ├── .gitignore ├── src ├── lib.rs ├── demo_04_read_file.rs ├── demo_09_02_match_if_Ok.rs ├── demo_09_01_option_if_some.rs ├── demo_03_read_file.rs ├── demo_08_02_option_ok_or.rs ├── demo_08_01_option_question_mark.rs ├── demo_06_01_multi_match.rs ├── demo_05_impl_error.rs ├── demo_06_02_custom_error.rs ├── demo_07_rename_result.rs └── demo_nom_error_handle.rs ├── LICENSE └── README.md /dat: -------------------------------------------------------------------------------- 1 | 8 -------------------------------------------------------------------------------- /handle_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baoyachi/rust-error-handle/HEAD/handle_error.png -------------------------------------------------------------------------------- /time_tpl.file: -------------------------------------------------------------------------------- 1 | time="2020/03/01 15:30:22" 2 | time = " 2020/03/01 15:30:22 " 3 | time = " 2020/03/01 15:30:22 " -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-handle-error" 3 | version = "0.1.0" 4 | authors = ["baoyachi "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | nom = "5.1.1" 11 | time = "0.2.7" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | .idea/* 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate time; 3 | 4 | 5 | mod demo_03_read_file; 6 | mod demo_04_read_file; 7 | mod demo_05_impl_error; 8 | mod demo_06_01_multi_match; 9 | mod demo_06_02_custom_error; 10 | mod demo_07_rename_result; 11 | mod demo_08_01_option_question_mark; 12 | mod demo_08_02_option_ok_or; 13 | mod demo_09_01_option_if_some; 14 | mod demo_09_02_match_if_ok; 15 | mod demo_nom_error_handle; -------------------------------------------------------------------------------- /src/demo_04_read_file.rs: -------------------------------------------------------------------------------- 1 | fn read_file(path: &str) -> Result { 2 | std::fs::read_to_string(path) 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | use super::*; 8 | 9 | #[test] 10 | fn test_read_file() { 11 | let path = "/tmp/dat"; 12 | match read_file(path) { 13 | Ok(file) => { println!("{}", file) } //OK 14 | Err(e) => { println!("{} {}", path, e) } //Error:/tmp/dat No such file or directory (os error 2) 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/demo_09_02_match_if_Ok.rs: -------------------------------------------------------------------------------- 1 | fn read_file(path: &str) -> Result { 2 | std::fs::read_to_string(path) 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | use super::*; 8 | 9 | #[test] 10 | fn test_read_fiel() { 11 | if let Ok(v) = read_file("./dat") { 12 | println!("{}", v); 13 | assert_eq!("8",v) 14 | } 15 | } 16 | } 17 | 18 | //fn main() { 19 | // if let Ok(v) = read_file("./dat") { 20 | // println!("{}", v); 21 | // } 22 | //} -------------------------------------------------------------------------------- /src/demo_09_01_option_if_some.rs: -------------------------------------------------------------------------------- 1 | fn opt_val(num: i32) -> Option { 2 | if num >= 60 { 3 | return Some("foo bar".to_string()); 4 | } 5 | None 6 | } 7 | 8 | #[cfg(test)] 9 | mod tests { 10 | use super::*; 11 | 12 | #[test] 13 | fn test_opt_val() { 14 | if let Some(v) = opt_val(60) { 15 | println!("{}", v); 16 | assert_eq!(v,"foo bar".to_string()); 17 | } 18 | } 19 | } 20 | 21 | 22 | //fn main() { 23 | // if let Some(v) = opt_val(60) { 24 | // println!("{}", v); 25 | // } 26 | //} -------------------------------------------------------------------------------- /src/demo_03_read_file.rs: -------------------------------------------------------------------------------- 1 | fn read_file(path: &str) -> String { 2 | std::fs::read_to_string(path).unwrap() 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | use super::*; 8 | 9 | #[test] 10 | fn test_read_file() { 11 | let path = "/tmp/dat"; 12 | //called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "No such file or directory" } 13 | //println!("{}", read_file(path)); 14 | } 15 | } 16 | 17 | //这是一个示例项目,使用`unit test`替代`main`函数执行 18 | //fn main() { 19 | // let path = "/tmp/dat"; 20 | // println!("{}", read_file(path)); 21 | //} -------------------------------------------------------------------------------- /src/demo_08_02_option_ok_or.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | enum Error { 3 | OptionError(String), 4 | } 5 | 6 | impl std::error::Error for Error {} 7 | 8 | impl std::fmt::Display for Error { 9 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 10 | match &self { 11 | Error::OptionError(ref e) => e.fmt(f), 12 | } 13 | } 14 | } 15 | 16 | pub type Result = std::result::Result; 17 | 18 | fn foo(index: i32) -> Option { 19 | if index > 60 { 20 | return Some("bar".to_string()); 21 | } 22 | None 23 | } 24 | 25 | #[cfg(test)] 26 | mod tests { 27 | use super::*; 28 | 29 | #[test] 30 | fn test_foo() -> Result<()> { 31 | let bar = foo(61) 32 | .ok_or(Error::OptionError("find `None` in foo() error".to_string()))?; 33 | assert_eq!("bar", bar); 34 | Ok(()) 35 | } 36 | } 37 | 38 | //fn main() -> Result<()> { 39 | // let bar = foo(60)?; 40 | // assert_eq!("bar", bar); 41 | // Ok(()) 42 | //} -------------------------------------------------------------------------------- /src/demo_08_01_option_question_mark.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | enum Error { 3 | OptionError(String), 4 | } 5 | 6 | impl std::error::Error for Error {} 7 | 8 | impl std::fmt::Display for Error { 9 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 10 | match &self { 11 | Error::OptionError(ref e) => e.fmt(f), 12 | } 13 | } 14 | } 15 | 16 | pub type Result = std::result::Result; 17 | 18 | fn foo(index: i32) -> Option { 19 | if index > 60 { 20 | return Some("bar".to_string()); 21 | } 22 | None 23 | } 24 | 25 | #[cfg(test)] 26 | mod tests { 27 | use super::*; 28 | 29 | #[test] 30 | fn test_foo() -> Result<()> { 31 | //painc eror: 32 | //cause:the trait `std::convert::From` is not implemented for `demo_08_option_question_mark::Error` 33 | // let bar = foo(60)?; 34 | // assert_eq!("bar", bar); 35 | Ok(()) 36 | } 37 | } 38 | 39 | //fn main() -> Result<()> { 40 | // let bar = foo(60)?; 41 | // assert_eq!("bar", bar); 42 | // Ok(()) 43 | //} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 baoyachi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/demo_06_01_multi_match.rs: -------------------------------------------------------------------------------- 1 | ///读取文件内容 2 | fn read_file(path: &str) -> Result { 3 | std::fs::read_to_string(path) 4 | } 5 | 6 | /// 转换为utf8内容 7 | fn to_utf8(v: &[u8]) -> Result<&str, std::str::Utf8Error> { 8 | std::str::from_utf8(v) 9 | } 10 | 11 | /// 转化为u32数字 12 | fn to_u32(v: &str) -> Result { 13 | v.parse::() 14 | } 15 | 16 | #[cfg(test)] 17 | mod tests { 18 | use super::*; 19 | 20 | #[test] 21 | fn test_to_u32() { 22 | let path = "./dat"; 23 | match read_file(path) { 24 | Ok(v) => { 25 | match to_utf8(v.as_bytes()) { 26 | Ok(u) => { 27 | match to_u32(u) { 28 | Ok(t) => { 29 | println!("num:{:?}", u); 30 | } 31 | Err(e) => { 32 | println!("{} {}", path, e) 33 | } 34 | } 35 | } 36 | Err(e) => { 37 | println!("{} {}", path, e) 38 | } 39 | } 40 | } 41 | Err(e) => { 42 | println!("{} {}", path, e) 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/demo_05_impl_error.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | ///自定义类型 Error,实现std::fmt::Debug的trait 4 | #[derive(Debug)] 5 | struct CustomError { 6 | err: ChildError, 7 | } 8 | 9 | ///实现Display的trait,并实现fmt方法 10 | impl std::fmt::Display for CustomError { 11 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 12 | write!(f, "CustomError is here!") 13 | } 14 | } 15 | 16 | ///实现Error的trait,因为有子Error:ChildError,需要覆盖source()方法,返回Some(err) 17 | impl std::error::Error for CustomError { 18 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 19 | Some(&self.err) 20 | } 21 | } 22 | 23 | 24 | ///子类型 Error,实现std::fmt::Debug的trait 25 | #[derive(Debug)] 26 | struct ChildError; 27 | 28 | ///实现Display的trait,并实现fmt方法 29 | impl std::fmt::Display for ChildError { 30 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 31 | write!(f, "ChildError is here!") 32 | } 33 | } 34 | 35 | ///实现Error的trait,因为没有子Error,不需要覆盖source()方法 36 | impl std::error::Error for ChildError {} 37 | 38 | ///构建一个Result的结果,返回自定义的error:CustomError 39 | fn get_super_error() -> Result<(), CustomError> { 40 | Err(CustomError { err: ChildError }) 41 | } 42 | 43 | #[cfg(test)] 44 | mod tests { 45 | use super::*; 46 | 47 | #[test] 48 | fn test_get_super_error() { 49 | match get_super_error() { 50 | Err(e) => { 51 | println!("Error: {}", e); 52 | println!("Caused by: {}", e.source().unwrap()); 53 | } 54 | _ => println!("No error"), 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /src/demo_06_02_custom_error.rs: -------------------------------------------------------------------------------- 1 | use std::io::Error as IoError; 2 | use std::str::Utf8Error; 3 | use std::num::ParseIntError; 4 | use std::fmt::{Display, Formatter}; 5 | 6 | ///读取文件内容 7 | fn read_file(path: &str) -> std::result::Result { 8 | std::fs::read_to_string(path) 9 | } 10 | 11 | /// 转换为utf8内容 12 | fn to_utf8(v: &[u8]) -> std::result::Result<&str, std::str::Utf8Error> { 13 | std::str::from_utf8(v) 14 | } 15 | 16 | /// 转化为u32数字 17 | fn to_u32(v: &str) -> std::result::Result { 18 | v.parse::() 19 | } 20 | 21 | 22 | #[derive(Debug)] 23 | enum CustomError { 24 | ParseIntError(std::num::ParseIntError), 25 | Utf8Error(std::str::Utf8Error), 26 | IoError(std::io::Error), 27 | } 28 | 29 | impl std::error::Error for CustomError { 30 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 31 | match &self { 32 | CustomError::IoError(ref e) => Some(e), 33 | CustomError::Utf8Error(ref e) => Some(e), 34 | CustomError::ParseIntError(ref e) => Some(e), 35 | } 36 | } 37 | } 38 | 39 | impl Display for CustomError { 40 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 41 | match &self { 42 | CustomError::IoError(ref e) => e.fmt(f), 43 | CustomError::Utf8Error(ref e) => e.fmt(f), 44 | CustomError::ParseIntError(ref e) => e.fmt(f), 45 | } 46 | } 47 | } 48 | 49 | impl From for CustomError { 50 | fn from(s: std::num::ParseIntError) -> Self { 51 | CustomError::ParseIntError(s) 52 | } 53 | } 54 | 55 | impl From for CustomError { 56 | fn from(s: std::io::Error) -> Self { 57 | CustomError::IoError(s) 58 | } 59 | } 60 | 61 | impl From for CustomError { 62 | fn from(s: std::str::Utf8Error) -> Self { 63 | CustomError::Utf8Error(s) 64 | } 65 | } 66 | 67 | #[cfg(test)] 68 | mod tests { 69 | use super::*; 70 | 71 | #[test] 72 | fn test_to_u32() -> std::result::Result<(), CustomError> { 73 | let path = "./dat"; 74 | let v = read_file(path)?; 75 | let x = to_utf8(v.as_bytes())?; 76 | let u = to_u32(x)?; 77 | assert_eq!(8, u); 78 | Ok(()) 79 | } 80 | } 81 | 82 | //fn main() -> Result<(),CustomError>{ 83 | // let path = "./dat"; 84 | // let v = read_file(path)?; 85 | // let x = to_utf8(v.as_bytes())?; 86 | // let u = to_u32(x)?; 87 | // println!("num:{:?}",u); 88 | // Ok(()) 89 | //} -------------------------------------------------------------------------------- /src/demo_07_rename_result.rs: -------------------------------------------------------------------------------- 1 | use std::io::Error as IoError; 2 | use std::str::Utf8Error; 3 | use std::num::ParseIntError; 4 | use std::fmt::{Display, Formatter}; 5 | 6 | ///读取文件内容 7 | fn _read_file(path: &str) -> std::result::Result { 8 | std::fs::read_to_string(path) 9 | } 10 | 11 | /// 转换为utf8内容 12 | fn _to_utf8(v: &[u8]) -> std::result::Result<&str, std::str::Utf8Error> { 13 | std::str::from_utf8(v) 14 | } 15 | 16 | /// 转化为u32数字 17 | fn _to_u32(v: &str) -> std::result::Result { 18 | v.parse::() 19 | } 20 | 21 | //----Convert Error 22 | 23 | #[derive(Debug)] 24 | enum CustomError { 25 | ParseIntError(std::num::ParseIntError), 26 | Utf8Error(std::str::Utf8Error), 27 | IoError(std::io::Error), 28 | } 29 | 30 | impl std::error::Error for CustomError { 31 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 32 | match &self { 33 | CustomError::IoError(ref e) => Some(e), 34 | CustomError::Utf8Error(ref e) => Some(e), 35 | CustomError::ParseIntError(ref e) => Some(e), 36 | } 37 | } 38 | } 39 | 40 | impl Display for CustomError { 41 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 42 | match &self { 43 | CustomError::IoError(ref e) => e.fmt(f), 44 | CustomError::Utf8Error(ref e) => e.fmt(f), 45 | CustomError::ParseIntError(ref e) => e.fmt(f), 46 | } 47 | } 48 | } 49 | 50 | impl From for CustomError { 51 | fn from(s: std::num::ParseIntError) -> Self { 52 | CustomError::ParseIntError(s) 53 | } 54 | } 55 | 56 | impl From for CustomError { 57 | fn from(s: std::io::Error) -> Self { 58 | CustomError::IoError(s) 59 | } 60 | } 61 | 62 | impl From for CustomError { 63 | fn from(s: std::str::Utf8Error) -> Self { 64 | CustomError::Utf8Error(s) 65 | } 66 | } 67 | 68 | ///自定义Result类型:IResult 69 | pub type IResult = std::result::Result; 70 | pub type IOResult = std::result::Result<(I, O), CustomError>; 71 | 72 | ///读取文件内容 73 | fn read_file(path: &str) -> IResult { 74 | let val = std::fs::read_to_string(path)?; 75 | Ok(val) 76 | } 77 | 78 | /// 转换为utf8内容 79 | fn to_utf8(v: &[u8]) -> IResult<&str> { 80 | let x = std::str::from_utf8(v)?; 81 | Ok(x) 82 | } 83 | 84 | /// 转化为u32数字 85 | fn to_u32(v: &str) -> IResult { 86 | let i = v.parse::()?; 87 | Ok(i) 88 | } 89 | 90 | #[cfg(test)] 91 | mod tests { 92 | use super::*; 93 | 94 | #[test] 95 | fn test_to_u32() -> std::result::Result<(), CustomError> { 96 | let path = "./dat"; 97 | let v = read_file(path)?; 98 | let x = to_utf8(v.as_bytes())?; 99 | let u = to_u32(x)?; 100 | assert_eq!(8, u); 101 | Ok(()) 102 | } 103 | 104 | } -------------------------------------------------------------------------------- /src/demo_nom_error_handle.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use nom::sequence::tuple; 3 | use nom::character::complete::{digit1, multispace0, multispace1}; 4 | use nom::bytes::complete::tag; 5 | use nom::combinator::map; 6 | use nom::character::complete::alphanumeric1; 7 | use time::PrimitiveDateTime; 8 | use time::ParseError as TimeParseError; 9 | use std::io::Error as IoError; 10 | use std::error::Error as StdError; 11 | use std::fmt::Display; 12 | use nom::error::ParseError; 13 | 14 | pub type Result = std::result::Result>; 15 | pub type IResult = std::result::Result<(I, O), Error>; 16 | 17 | 18 | #[derive(Debug)] 19 | pub struct Error { 20 | kind: ErrorKind, 21 | error: nom::error::VerboseError, 22 | incomplete: String, 23 | 24 | } 25 | 26 | impl Error { 27 | pub fn new(kind: ErrorKind) -> Error { 28 | Error { 29 | kind, 30 | error: nom::error::VerboseError { errors: vec![] }, 31 | incomplete: "".to_string(), 32 | } 33 | } 34 | 35 | pub fn new_nom_error(input: I, kind: nom::error::ErrorKind) -> Self { 36 | Self { 37 | kind: ErrorKind::NomParserError("Nom parser error".to_string()), 38 | error: nom::error::VerboseError::from_error_kind(input, kind), 39 | incomplete: "".to_string(), 40 | } 41 | } 42 | } 43 | 44 | 45 | #[derive(Debug)] 46 | pub enum ErrorKind { 47 | IoError(IoError), 48 | TimeParseError(TimeParseError), 49 | NomParserError(String), 50 | StringError(String), 51 | EmptyError, 52 | } 53 | 54 | impl StdError for Error { 55 | fn source(&self) -> Option<&(dyn StdError + 'static)> { 56 | match &self.kind { 57 | ErrorKind::IoError(ref e) => Some(e), 58 | ErrorKind::TimeParseError(ref e) => Some(e), 59 | ErrorKind::NomParserError(_) => None, 60 | ErrorKind::StringError(_) => None, 61 | ErrorKind::EmptyError => None, 62 | } 63 | } 64 | } 65 | 66 | impl Display for Error { 67 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 68 | match &self.kind { 69 | ErrorKind::IoError(ref e) => e.fmt(f), 70 | ErrorKind::TimeParseError(ref e) => e.fmt(f), 71 | ErrorKind::NomParserError(ref e) => e.fmt(f), 72 | ErrorKind::StringError(ref e) => e.fmt(f), 73 | ErrorKind::EmptyError => write!(f, "Empty Error"), 74 | } 75 | } 76 | } 77 | 78 | impl From for Error { 79 | fn from(e: TimeParseError) -> Self { 80 | Error::new(ErrorKind::TimeParseError(e)) 81 | } 82 | } 83 | 84 | impl From for Error { 85 | fn from(s: std::string::String) -> Self { 86 | Error::new(ErrorKind::StringError(s)) 87 | } 88 | } 89 | 90 | 91 | impl From for Error { 92 | fn from(s: IoError) -> Self { 93 | Error::new(ErrorKind::IoError(s)) 94 | } 95 | } 96 | 97 | impl<'a> From> for Error<()> { 98 | fn from(s: Error<&'a str>) -> Self { 99 | Error::new(ErrorKind::StringError(format!( 100 | "kind:{:?},error:{:?}", 101 | s.kind, s.error 102 | ))) 103 | } 104 | } 105 | 106 | 107 | impl From> for Error { 108 | fn from(i: nom::Err<(I, nom::error::ErrorKind)>) -> Self { 109 | match i { 110 | nom::Err::Error(err) | nom::Err::Failure(err) => Error::new_nom_error(err.0, err.1), 111 | nom::Err::Incomplete(i) => Error::new(ErrorKind::StringError(format!( 112 | "Nom parser Incomplete error: {:?}", 113 | i 114 | ))), 115 | } 116 | } 117 | } 118 | 119 | 120 | impl Into> for Error { 121 | fn into(self) -> nom::error::VerboseError { 122 | self.error 123 | } 124 | } 125 | 126 | impl nom::error::ParseError for Error { 127 | fn from_error_kind(input: I, kind: nom::error::ErrorKind) -> Self { 128 | Error::new_nom_error(input, kind) 129 | } 130 | 131 | fn append(input: I, kind: nom::error::ErrorKind, mut other: Self) -> Self { 132 | other 133 | .error 134 | .errors 135 | .push((input, nom::error::VerboseErrorKind::Nom(kind))); 136 | other 137 | } 138 | 139 | fn from_char(input: I, c: char) -> Self { 140 | Self { 141 | kind: ErrorKind::EmptyError, 142 | error: nom::error::VerboseError::from_char(input, c), 143 | incomplete: "".to_string(), 144 | } 145 | } 146 | 147 | fn add_context(input: I, ctx: &'static str, mut other: Self) -> Self { 148 | other 149 | .error 150 | .errors 151 | .push((input, nom::error::VerboseErrorKind::Context(ctx))); 152 | other 153 | } 154 | } 155 | 156 | 157 | // nom parser:2020/03/01 15:30:22 -> 2020-03-01 15:30:22 158 | fn parser_time(input: &str) -> IResult<&str, (String, String)> { 159 | //"2020/03/01 15:30:22" or " 2020/03/01 15:30:22 " 160 | let time_value = tuple(( 161 | tag("\""), 162 | multispace0, 163 | digit1, 164 | tag("/"), 165 | digit1, 166 | tag("/"), 167 | digit1, 168 | multispace1, 169 | digit1, 170 | tag(":"), 171 | digit1, 172 | tag(":"), 173 | digit1, 174 | multispace0, 175 | tag("\""), 176 | multispace0, 177 | )); 178 | 179 | //time = "2020-03-01 15:30:22" 180 | let (input, (_, y1, _, _, _, (_, _, y2, _, y3, _, y4, _, y5, _, y6, _, y7, _, _, _, ))) = tuple(( 181 | multispace0, 182 | alphanumeric1, 183 | multispace0, 184 | tag("="), 185 | multispace0, 186 | time_value 187 | ))(input)?; 188 | 189 | let time = format!("{}-{}-{} {}:{}:{}", y2, y3, y4, y5, y6, y7); 190 | Ok((input, (y1.to_string(), time))) 191 | } 192 | 193 | // parser time 194 | fn get_time<'a>(content: &'a str) -> IResult<&'a str, (String, PrimitiveDateTime)> { 195 | let (input, (key, value)) = parser_time(content)?; 196 | let time = PrimitiveDateTime::parse(value, "%F %T")?; 197 | Ok((input, (key, time))) 198 | } 199 | 200 | #[cfg(test)] 201 | mod tests { 202 | use super::*; 203 | use time::Date; 204 | 205 | #[test] 206 | fn test_parser_time() -> Result<()>{ 207 | let input = r#" 208 | time = "2020/03/01 15:30:22" 209 | "#; 210 | let (input, (key, value)) = parser_time(input)?; 211 | assert_eq!(input, ""); 212 | assert_eq!(key, "time"); 213 | assert_eq!(value, "2020-03-01 15:30:22"); 214 | Ok(()) 215 | } 216 | 217 | #[test] 218 | fn test_convert_time() -> Result<()>{ 219 | let path = "./time_tpl.file"; 220 | let content = fs::read_to_string(path)?; 221 | for line in content.clone().lines().into_iter() { 222 | if line.clone().trim().is_empty() { 223 | continue; 224 | } 225 | let (input, (key, value)) = get_time(line)?; 226 | assert_eq!(input, ""); 227 | assert_eq!(key, "time"); 228 | assert_eq!(value, PrimitiveDateTime::new(date!(2020-03-01), time!(15:30:22))); 229 | } 230 | Ok(()) 231 | } 232 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 细说Rust错误处理 2 | 3 | - [细说Rust错误处理](#%e7%bb%86%e8%af%b4rust%e9%94%99%e8%af%af%e5%a4%84%e7%90%86) 4 | - [1. 前言](#1-%e5%89%8d%e8%a8%80) 5 | - [2. 背景](#2-%e8%83%8c%e6%99%af) 6 | - [3. unwrap的危害!](#3-unwrap%e7%9a%84%e5%8d%b1%e5%ae%b3) 7 | - [4. 对比语言处理错误](#4-%e5%af%b9%e6%af%94%e8%af%ad%e8%a8%80%e5%a4%84%e7%90%86%e9%94%99%e8%af%af) 8 | - [4.1 golang的错误处理演示](#41-golang%e7%9a%84%e9%94%99%e8%af%af%e5%a4%84%e7%90%86%e6%bc%94%e7%a4%ba) 9 | - [4.2 Rust 错误处理示例](#42-rust-%e9%94%99%e8%af%af%e5%a4%84%e7%90%86%e7%a4%ba%e4%be%8b) 10 | - [5. Rust中的错误处理](#5-rust%e4%b8%ad%e7%9a%84%e9%94%99%e8%af%af%e5%a4%84%e7%90%86) 11 | - [6. 自定义Error转换:From](#6-%e8%87%aa%e5%ae%9a%e4%b9%89error%e8%bd%ac%e6%8d%a2from) 12 | - [7. 重命名Result](#7-%e9%87%8d%e5%91%bd%e5%90%8dresult) 13 | - [8. Option转换](#8-option%e8%bd%ac%e6%8d%a2) 14 | - [9. 避免unwrap()](#9-%e9%81%bf%e5%85%8dunwrap) 15 | - [10. 自定义Error同级转换](#10-%e8%87%aa%e5%ae%9a%e4%b9%89error%e5%90%8c%e7%ba%a7%e8%bd%ac%e6%8d%a2) 16 | - [11. Error常见开源库](#11-error%e5%b8%b8%e8%a7%81%e5%bc%80%e6%ba%90%e5%ba%93) 17 | - [12. 参考链接](#12-%e5%8f%82%e8%80%83%e9%93%be%e6%8e%a5) 18 | - [13 错误处理实战](#13-%e9%94%99%e8%af%af%e5%a4%84%e7%90%86%e5%ae%9e%e6%88%98) 19 | - [14. 总结](#14-%e6%80%bb%e7%bb%93) 20 | 21 | ![handle-error.png](https://github.com/baoyachi/rust-error-handle/raw/master/handle_error.png) 22 | 23 | 原文地址:[https://github.com/baoyachi/rust-error-handle](https://github.com/baoyachi/rust-error-handle) 24 | 25 | 26 | ## 1. 前言 27 | 这篇文章写得比较长,全文读完大约需要15-20min,如果对`Rust`的错误处理不清楚或还有些许模糊的同学,请静下心来细细阅读。当读完该篇文章后,可以说对`Rust`的错误处理可以做到掌握自如。 28 | 29 | 笔者花费较长篇幅来描述**错误处理**的来去,详细介绍其及一步步梳理内容,望大家能耐心读完后对大家有所帮助。当然,在写这篇文章之时,也借阅了大量互联网资料,详见链接见底部**参考链接** 30 | 31 | 掌握好`Rust`的错误设计,不仅可以提升我们对错误处理的认识,对代码结构、层次都有很大的帮助。那废话不多说,那我们开启这段阅读之旅吧😄! 32 | 33 | ## 2. 背景 34 | 笔者在写这篇文章时,也翻阅一些资料关于`Rust`的错误处理资料,多数是对其一笔带过,导致之前接触过其他语言的新同学来说,上手处理`Rust`的错误会有**当头棒喝**的感觉。找些资料发现**unwrap()**也可以解决问题,然后心中暗自窃喜,程序在运行过程中,**因为忽略检查或程序逻辑判断**,导致某些情况,程序**panic**。这可能是我们最不愿看到的现象,遂又回到起点,重新去了解`Rust`的错误处理。 35 | 36 | 这篇文章,通过一步步介绍,让大家清晰知道`Rust`的错误处理的究竟。介绍在`Rust`中的错误使用及如何处理错误,以及在实际工作中关于其使用技巧。 37 | 38 | ## 3. unwrap的危害! 39 | 下面我们来看一段代码,执行一下: 40 | 41 | ```rust 42 | fn main() { 43 | let path = "/tmp/dat"; 44 | println!("{}", read_file(path)); 45 | } 46 | 47 | fn read_file(path: &str) -> String { 48 | std::fs::read_to_string(path).unwrap() 49 | } 50 | ``` 51 | 程序执行结果: 52 | ```bash 53 | 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 54 | stack backtrace: 55 | 0: backtrace::backtrace::libunwind::trace 56 | at /Users/runner/.cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.40/src/backtrace/libunwind.rs:88 57 | ... 58 | 15: rust_sugar::read_file 59 | at src/main.rs:7 60 | 16: rust_sugar::main 61 | at src/main.rs:3 62 | ... 63 | 25: rust_sugar::read_file 64 | note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. 65 | ``` 66 | 67 | 什么,因为`path`路径不对,程序竟然崩溃了,这个是我们不能接受的! 68 | 69 | **unwrap()** 这个操作在rust代码中,应该看过很多这种代码,甚至此时我们正在使用它。它主要用于`Option`或`Result`的打开其包装的结果。常常我们在代码中,使用简单,或快速处理,使用了 **unwrap()** 的操作,但是,它是一个非常危险的信号! 70 | 71 | 可能因为**没有程序检查或校验**,潜在的bug可能就出现其中,使得我们程序往往就**panic**了。这可能使我们最不愿看到的现象。 72 | 73 | 在实际项目开发中,程序中可能充斥着大量代码,我们很难避免**unwrap()**的出现,为了解决这种问题,我们通过做**code review**,或使用脚本工具检查其降低其出现的可能性。 74 | 75 | 通常每个项目都有一些约束,或许:在大型项目开发中, 不用**unwrap()** 方法,使用其他方式处理程序,**unwrap()** 的不出现可能会使得程序的健壮性高出很多。 76 | 77 | 这里前提是团队或大型项目,如果只是写一个简单例子(demo)就不在本篇文章的讨论范畴。因为一个Demo的问题,可能只是快速**示范或演示**,不考虑程序健壮性, **unwrap()** 的操作可能会更方便代码表达。 78 | 79 | 可能有人会问,我们通常跑程序**unit test**,其中的很多**mock**数据会有 **unwrap()** 的操作,我们只是为了在单元测试中使得程序简单。这种也能不使用吗?答案:是的,完全可以不使用 **unwrap()** 也可以做到的。 80 | 81 | ## 4. 对比语言处理错误 82 | 说到**unwrap()**,我们不得不提到`rust`的错误处理,**unwrap()** 和`Rust`的错误处理是密不可分的。 83 | 84 | ### 4.1 golang的错误处理演示 85 | 86 | 如果了解`golang`的话,应该清楚下面这段代码的意思: 87 | ```go 88 | package main 89 | 90 | import ( 91 | "io/ioutil" 92 | "log" 93 | ) 94 | 95 | func main() { 96 | path := "/tmp/dat" //文件路径 97 | file, err := readFile(path) 98 | if err != nil { 99 | log.Fatal(err) //错误打印 100 | } 101 | println("%s", file) //打印文件内容 102 | } 103 | 104 | func readFile(path string) (string, error) { 105 | dat, err := ioutil.ReadFile(path) //读取文件内容 106 | if err != nil { //判断err是否为nil 107 | return "", err //不为nil,返回err结果 108 | } 109 | return string(dat), nil //err=nil,返回读取文件内容 110 | } 111 | ``` 112 | 我们执行下程序,打印如下。执行错误,当然,因为我们给的文件路径不存在,程序报错。 113 | ```bash 114 | 2020/02/24 01:24:04 open /tmp/dat: no such file or directory 115 | ``` 116 | 117 | 这里,`golang`采用多返回值方式,程序报错返回错误问题,通过判断 **err!=nil** 来决定程序是否继续执行或终止该逻辑。当然,如果接触过`golang`项目时,会发现程序中大量充斥着`if err!=nil`的代码,对此网上有对`if err!=nil`进行了很多讨论,因为这个不在本篇文章的范畴中,在此不对其追溯、讨论。 118 | 119 | ### 4.2 Rust 错误处理示例 120 | 对比了`golang`代码,我们对照上面的例子,看下在`Rust`中如何编写这段程序,代码如下: 121 | ```rust 122 | fn main() { 123 | let path = "/tmp/dat"; //文件路径 124 | match read_file(path) { //判断方法结果 125 | Ok(file) => { println!("{}", file) } //OK 代表读取到文件内容,正确打印文件内容 126 | Err(e) => { println!("{} {}", path, e) } //Err代表结果不存在,打印错误结果 127 | } 128 | } 129 | 130 | fn read_file(path: &str) -> Result { //Result作为结果返回值 131 | std::fs::read_to_string(path) //读取文件内容 132 | } 133 | ``` 134 | 当前,因为我们给的文件路径不存在,程序报错,打印内容如下: 135 | ```bash 136 | No such file or directory (os error 2) 137 | ``` 138 | 139 | 在`Rust`代表中,`Result`是一个`enum`枚举对象,部分源码如下: 140 | 141 | ```rust 142 | pub enum Result { 143 | /// Contains the success value 144 | Ok(#[stable(feature = "rust1", since = "1.0.0")] T), 145 | 146 | /// Contains the error value 147 | Err(#[stable(feature = "rust1", since = "1.0.0")] E), 148 | } 149 | ``` 150 | 通常我们使用`Result`的枚举对象作为程序的返回值,通过`Result`来判断其结果,我们使用`match`匹配的方式来获取`Result`的内容,判断正常(Ok)或错误(Err)。 151 | 152 | 或许,我们大致向上看去,`golang`代码和`Rust`代码没有本质区别,都是采用返回值方式,给出程序结果。下面我们就对比两种语言说说之间区别: 153 | 154 | * `golang`采用多返回值方式,我们在拿到目标结果时(上面是指文件内容*file*),需要首先对`err`判断是否为`nil`,并且我们在`return`时,需要给**多返回值**分别赋值,调用时需要对 `if err!=nil` 做结果判断。 155 | * `Rust`中采用`Result`的枚举对象做结果返回。枚举的好处是:多选一。因为`Result`的枚举类型为`Ok`和`Err`,使得我们每次在返回`Result`的结果时,要么是`Ok`,要么是`Err`。它不需要`return`结果同时给两个值赋值,这样的情况只会存在一种可能性: **Ok or Err** 。 156 | * golang的函数调用需要对 `if err!=nil`做结果判断,因为这段代码 判断是**手动逻辑**,往往我们可能因为疏忽,导致这段逻辑缺失,缺少校验。当然,我们在编写代码期间可以通过某些工具 `lint` 扫描出这种潜在bug。 157 | * `Rust`的`match`判断是自动打开,当然你也可以选择忽略其中某一个枚举值,我们不在此说明。 158 | 159 | 可能有人发现,如果我有多个函数,需要多个函数的执行结果,这样需要`match`代码多次,代码会不会是一坨一坨,显得代码很臃肿,难看。是的,这个问题提出的的确是有这种问题,不过这个在后面我们讲解的时候,会通过程序语法糖避免多次`match`多次结果的问题,不过我们在此先不叙说,后面将有介绍。 160 | 161 | 162 | ## 5. Rust中的错误处理 163 | 前面不管是`golang`还是`Rust`采用`return`返回值方式,两者都是为了解决程序中错误处理的问题。好了,前面说了这么多,我们还是回归正题:Rust中是如何对错误进行处理的? 164 | 165 | 要想细致了解`Rust`的错误处理,我们需要了解`std::error::Error`,该trait的内部方法,部分代码如下: 166 | 参考链接:[https://doc.rust-lang.org/std/error/trait.Error.html](https://doc.rust-lang.org/std/error/trait.Error.html) 167 | 168 | ```rust 169 | pub trait Error: Debug + Display { 170 | 171 | fn description(&self) -> &str { 172 | "description() is deprecated; use Display" 173 | } 174 | 175 | #[rustc_deprecated(since = "1.33.0", reason = "replaced by Error::source, which can support \ 176 | downcasting")] 177 | 178 | fn cause(&self) -> Option<&dyn Error> { 179 | self.source() 180 | } 181 | 182 | fn source(&self) -> Option<&(dyn Error + 'static)> { None } 183 | 184 | #[doc(hidden)] 185 | fn type_id(&self, _: private::Internal) -> TypeId where Self: 'static { 186 | TypeId::of::() 187 | } 188 | 189 | #[unstable(feature = "backtrace", issue = "53487")] 190 | fn backtrace(&self) -> Option<&Backtrace> { 191 | None 192 | } 193 | } 194 | ``` 195 | 196 | * `description()`在文档介绍中,尽管使用它不会导致编译警告,但新代码应该实现`impl Display` ,新`impl`的可以省略,不用实现该方法, 要获取字符串形式的错误描述,请使用`to_string()`。 197 | * `cause()`在**1.33.0**被抛弃,取而代之使用`source()`方法,新`impl`的不用实现该方法。 198 | * `source()`此错误的低级源,如果内部有错误类型`Err`返回:`Some(e)`,如果没有返回:`None`。 199 | 200 | * 如果当前`Error`是低级别的`Error`,并没有**子Error**,需要返回`None`。介于其本身默认有返回值`None`,可以**不覆盖**该方法。 201 | * 如果当前`Error`包含**子Error**,需要返回**子Error**:`Some(err)`,需要**覆盖**该方法。 202 | * `type_id()`该方法被隐藏。 203 | * `backtrace()`返回发生此错误的堆栈追溯,因为标记`unstable`,在`Rust`的`stable`版本不被使用。 204 | * 自定义的`Error`需要**impl std::fmt::Debug**的trait,当然我们只需要在默认对象上添加注解:`#[derive(Debug)]`即可。 205 | 206 | 207 | 总结一下,自定义一个`error`需要实现如下几步: 208 | 209 | * 手动实现impl `std::fmt::Display`的trait,并**实现** `fmt(...)`方法。 210 | * 手动实现impl `std::fmt::Debug`的`trait`,一般直接添加注解即可:`#[derive(Debug)]` 211 | * 手动实现impl `std::error::Error`的`trait`,并根据自身`error`级别是否**覆盖**`std::error::Error`中的`source()`方法。 212 | 213 | 下面我们自己手动实现下`Rust`的**自定义错误:CustomError** 214 | ```rust 215 | use std::error::Error; 216 | 217 | ///自定义类型 Error,实现std::fmt::Debug的trait 218 | #[derive(Debug)] 219 | struct CustomError { 220 | err: ChildError, 221 | } 222 | 223 | ///实现Display的trait,并实现fmt方法 224 | impl std::fmt::Display for CustomError { 225 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 226 | write!(f, "CustomError is here!") 227 | } 228 | } 229 | 230 | ///实现Error的trait,因为有子Error:ChildError,需要覆盖source()方法,返回Some(err) 231 | impl std::error::Error for CustomError { 232 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 233 | Some(&self.err) 234 | } 235 | } 236 | 237 | 238 | ///子类型 Error,实现std::fmt::Debug的trait 239 | #[derive(Debug)] 240 | struct ChildError; 241 | 242 | ///实现Display的trait,并实现fmt方法 243 | impl std::fmt::Display for ChildError { 244 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 245 | write!(f, "ChildError is here!") 246 | } 247 | } 248 | 249 | ///实现Error的trait,因为没有子Error,不需要覆盖source()方法 250 | impl std::error::Error for ChildError {} 251 | 252 | ///构建一个Result的结果,返回自定义的error:CustomError 253 | fn get_super_error() -> Result<(), CustomError> { 254 | Err(CustomError { err: ChildError }) 255 | } 256 | 257 | fn main() { 258 | match get_super_error() { 259 | Err(e) => { 260 | println!("Error: {}", e); 261 | println!("Caused by: {}", e.source().unwrap()); 262 | } 263 | _ => println!("No error"), 264 | } 265 | } 266 | ``` 267 | * `ChildError`为子类型`Error`,**没有覆盖**`source()`方法,空实现了`std::error::Error` 268 | * `CustomError`有子类型`ChildError`,**覆盖**了`source()`,并返回了子类型Option值:`Some(&self.err)` 269 | 270 | 运行执行结果,显示如下: 271 | ```bash 272 | Error: CustomError is here! 273 | Caused by: ChildError is here! 274 | ``` 275 | 至此,我们就了解了如何实现`Rust`中**自定义Error**了。 276 | 277 | 278 | ## 6. 自定义Error转换:From 279 | 上面我们说到,函数返回`Result`的结果时,需要获取函数的返回值是成功(Ok)还是失败(Err),需要使用`match`匹配,我们看下多函数之间调用是如何解决这类问题的?假设我们有个场景: 280 | * 读取一文件 281 | * 将文件内容转化为`UTF8`格式 282 | * 将转换后格式内容转为`u32`的数字。 283 | 284 | 所以我们有了下面三个函数(省略部分代码): 285 | ```rust 286 | ... 287 | 288 | ///读取文件内容 289 | fn read_file(path: &str) -> Result { 290 | std::fs::read_to_string(path) 291 | } 292 | 293 | /// 转换为utf8内容 294 | fn to_utf8(v: &[u8]) -> Result<&str, std::str::Utf8Error> { 295 | std::str::from_utf8(v) 296 | } 297 | 298 | /// 转化为u32数字 299 | fn to_u32(v: &str) -> Result { 300 | v.parse::() 301 | } 302 | ``` 303 | 304 | 最终,我们得到`u32`的数字,对于该场景如何组织我们代码呢? 305 | 306 | * `unwrap()`直接打开三个方法,取出值。这种方式太暴力,并且会有`bug`,造成程序`panic`,不被采纳。 307 | * `match`匹配,如何返回OK,继续下一步,否则报错终止逻辑,那我们试试。 308 | 309 | 参考代码如下: 310 | ```rust 311 | fn main() { 312 | let path = "./dat"; 313 | match read_file(path) { 314 | Ok(v) => { 315 | match to_utf8(v.as_bytes()) { 316 | Ok(u) => { 317 | match to_u32(u) { 318 | Ok(t) => { 319 | println!("num:{:?}", u); 320 | } 321 | Err(e) => { 322 | println!("{} {}", path, e) 323 | } 324 | } 325 | } 326 | Err(e) => { 327 | println!("{} {}", path, e) 328 | } 329 | } 330 | } 331 | Err(e) => { 332 | println!("{} {}", path, e) 333 | } 334 | } 335 | } 336 | 337 | ///读取文件内容 338 | fn read_file(path: &str) -> Result { 339 | std::fs::read_to_string(path) 340 | } 341 | 342 | /// 转换为utf8内容 343 | fn to_utf8(v: &[u8]) -> Result<&str, std::str::Utf8Error> { 344 | std::str::from_utf8(v) 345 | } 346 | 347 | /// 转化为u32数字 348 | fn to_u32(v: &str) -> Result { 349 | v.parse::() 350 | } 351 | ``` 352 | 353 | 天啊,虽然是实现了上面场景的需求,但是代码犹如叠罗汉,程序结构越来越深啊,这个是我们没法接受的!`match`匹配导致程序如此**不堪一击**。那么有没有第三种方法呢?当然是有的:`From`转换。 354 | 355 | 前面我们说到如何**自定义的Error**,如何我们将上面三个`error`收纳到我们**自定义的Error**中,将它们三个`Error`变成**自定义Error**的**子Error**,这样我们对外的`Result`统一返回**自定义的Error**。这样程序应该可以改变点什么,我们来试试吧。 356 | ```rust 357 | #[derive(Debug)] 358 | enum CustomError { 359 | ParseIntError(std::num::ParseIntError), 360 | Utf8Error(std::str::Utf8Error), 361 | IoError(std::io::Error), 362 | } 363 | impl std::error::Error for CustomError{ 364 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 365 | match &self { 366 | CustomError::IoError(ref e) => Some(e), 367 | CustomError::Utf8Error(ref e) => Some(e), 368 | CustomError::ParseIntError(ref e) => Some(e), 369 | } 370 | } 371 | } 372 | 373 | impl Display for CustomError{ 374 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 375 | match &self { 376 | CustomError::IoError(ref e) => e.fmt(f), 377 | CustomError::Utf8Error(ref e) => e.fmt(f), 378 | CustomError::ParseIntError(ref e) => e.fmt(f), 379 | } 380 | } 381 | } 382 | 383 | impl From for CustomError { 384 | fn from(s: std::num::ParseIntError) -> Self { 385 | CustomError::ParseIntError(s) 386 | } 387 | } 388 | 389 | impl From for CustomError { 390 | fn from(s: std::io::Error) -> Self { 391 | CustomError::IoError(s) 392 | } 393 | } 394 | 395 | impl From for CustomError { 396 | fn from(s: std::str::Utf8Error) -> Self { 397 | CustomError::Utf8Error(s) 398 | } 399 | } 400 | ``` 401 | 402 | * `CustomError`为我们实现的**自定义Error** 403 | * `CustomError`有三个**子类型Error** 404 | * `CustomError`分别实现了三个**子类型Error** `From`的trait,将其类型包装为**自定义Error**的子类型 405 | 406 | 好了,有了自定义的`CustomError`,那怎么使用呢? 我们看代码: 407 | 408 | ```rust 409 | use std::io::Error as IoError; 410 | use std::str::Utf8Error; 411 | use std::num::ParseIntError; 412 | use std::fmt::{Display, Formatter}; 413 | 414 | 415 | fn main() -> std::result::Result<(),CustomError>{ 416 | let path = "./dat"; 417 | let v = read_file(path)?; 418 | let x = to_utf8(v.as_bytes())?; 419 | let u = to_u32(x)?; 420 | println!("num:{:?}",u); 421 | Ok(()) 422 | } 423 | 424 | ///读取文件内容 425 | fn read_file(path: &str) -> std::result::Result { 426 | std::fs::read_to_string(path) 427 | } 428 | 429 | /// 转换为utf8内容 430 | fn to_utf8(v: &[u8]) -> std::result::Result<&str, std::str::Utf8Error> { 431 | std::str::from_utf8(v) 432 | } 433 | 434 | /// 转化为u32数字 435 | fn to_u32(v: &str) -> std::result::Result { 436 | v.parse::() 437 | } 438 | 439 | 440 | #[derive(Debug)] 441 | enum CustomError { 442 | ParseIntError(std::num::ParseIntError), 443 | Utf8Error(std::str::Utf8Error), 444 | IoError(std::io::Error), 445 | } 446 | impl std::error::Error for CustomError{ 447 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 448 | match &self { 449 | CustomError::IoError(ref e) => Some(e), 450 | CustomError::Utf8Error(ref e) => Some(e), 451 | CustomError::ParseIntError(ref e) => Some(e), 452 | } 453 | } 454 | } 455 | 456 | impl Display for CustomError{ 457 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 458 | match &self { 459 | CustomError::IoError(ref e) => e.fmt(f), 460 | CustomError::Utf8Error(ref e) => e.fmt(f), 461 | CustomError::ParseIntError(ref e) => e.fmt(f), 462 | } 463 | } 464 | } 465 | 466 | impl From for CustomError { 467 | fn from(s: std::num::ParseIntError) -> Self { 468 | CustomError::ParseIntError(s) 469 | } 470 | } 471 | 472 | impl From for CustomError { 473 | fn from(s: std::io::Error) -> Self { 474 | CustomError::IoError(s) 475 | } 476 | } 477 | 478 | impl From for CustomError { 479 | fn from(s: std::str::Utf8Error) -> Self { 480 | CustomError::Utf8Error(s) 481 | } 482 | } 483 | ``` 484 | 485 | 其实我们主要关心的是这段代码: 486 | ```rust 487 | fn main() -> Result<(),CustomError>{ 488 | let path = "./dat"; 489 | let v = read_file(path)?; 490 | let x = to_utf8(v.as_bytes())?; 491 | let u = to_u32(x)?; 492 | println!("num:{:?}",u); 493 | Ok(()) 494 | } 495 | ``` 496 | 我们使用了`?`来替代原来的`match`匹配的方式。`?`使用问号作用在函数的结束,意思是: 497 | 498 | * 程序接受了一个`Result<(),CustomError>`自定义的错误类型。 499 | * 当前如果函数结果错误,程序自动抛出`Err`自身错误类型,并包含相关自己类型错误信息,因为我们做了`From`转换的操作,该函数的自身类型错误会通过实现的`From`操作自动转化为`CustomError`的自定义类型错误。 500 | * 当前如果函数结果正确,继续之后逻辑,直到程序结束。 501 | 502 | 这样,我们通过`From`和`?`解决了之前`match`匹配代码层级深的问题,因为这种转换是**无感知**的,使得我们在处理好错误类型后,只需要关心我们的目标值即可,这样不需要显示对`Err(e)`的数据单独处理,使得我们在函数后添加`?`后,程序一切都是自动了。 503 | 504 | 还记得我们之前讨论在对比`golang`的错误处理时的:`if err!=nil`的逻辑了吗,这种因为用了`?`语法糖使得该段判断将不再存在。 505 | 506 | 另外,我们还注意到,`Result`的结果可以作用在`main`函数上, 507 | 508 | * 是的,`Result`的结果不仅能作用在`main`函数上 509 | * `Result`还可以作用在单元测试上,这就是我们文中刚开始提到的:因为有了`Result`的作用,使得我们在程序中几乎可以完全摒弃`unwrap()`的代码块,使得程序更轻,大大减少潜在问题,程序组织结构更加清晰。 510 | 511 | 下面这是作用在单元测试上的`Result`的代码: 512 | ```rust 513 | ... 514 | 515 | #[cfg(test)] 516 | mod tests { 517 | use super::*; 518 | 519 | #[test] 520 | fn test_get_num() -> std::result::Result<(), CustomError> { 521 | let path = "./dat"; 522 | let v = read_file(path)?; 523 | let x = to_utf8(v.as_bytes())?; 524 | let u = to_u32(x)?; 525 | assert_eq!(u, 8); 526 | Ok(()) 527 | } 528 | } 529 | ``` 530 | 531 | ## 7. 重命名Result 532 | 我们在实际项目中,会大量使用如上的`Result`结果,并且`Result`的`Err`类型是我们`自定义错误`,导致我们写程序时会显得非常**啰嗦**、**冗余** 533 | ```rust 534 | ///读取文件内容 535 | fn read_file(path: &str) -> std::result::Result { 536 | let val = std::fs::read_to_string(path)?; 537 | Ok(val) 538 | } 539 | 540 | /// 转换为utf8内容 541 | fn to_utf8(v: &[u8]) -> std::result::Result<&str, CustomError> { 542 | let x = std::str::from_utf8(v)?; 543 | Ok(x) 544 | } 545 | 546 | /// 转化为u32数字 547 | fn to_u32(v: &str) -> std::result::Result { 548 | let i = v.parse::()?; 549 | Ok(i) 550 | } 551 | ``` 552 | 我们的程序中,会大量充斥着这种**模板代码**,`Rust`本身支持对类型自定义,使得我们只需要重命名`Result`即可: 553 | ```rust 554 | pub type IResult = std::result::Result; ///自定义Result类型:IResult 555 | ``` 556 | 这样,凡是使用的是自定义类型错误的`Result`都可以使用`IResult`来替换`std::result::Result`的类型,使得简化程序,隐藏`Error`类型及细节,关注目标主体,代码如下: 557 | ```rust 558 | ///读取文件内容 559 | fn read_file(path: &str) -> IResult { 560 | let val = std::fs::read_to_string(path)?; 561 | Ok(val) 562 | } 563 | 564 | /// 转换为utf8内容 565 | fn to_utf8(v: &[u8]) -> IResult<&str> { 566 | let x = std::str::from_utf8(v)?; 567 | Ok(x) 568 | } 569 | 570 | /// 转化为u32数字 571 | fn to_u32(v: &str) -> IResult { 572 | let i = v.parse::()?; 573 | Ok(i) 574 | } 575 | ``` 576 | 将`std::result::Result` 替换为:`IResult`类型 577 | 578 | 当然,会有人提问,如果是多参数类型怎么处理呢,同样,我们只需将`OK`类型变成 **tuple** `(I,O)`类型的多参数数据即可,大概这样: 579 | ```rust 580 | pub type IResult = std::result::Result<(I, O), CustomError>; 581 | ``` 582 | 583 | 使用也及其简单,只需要返回:**I**,**O**的具体类型,举个示例: 584 | ```rust 585 | fn foo() -> IResult { 586 | Ok((String::from("bar"), 32)) 587 | } 588 | ``` 589 | 590 | 使用重命名类型的`Result`,使得我们错误类型统一,方便处理。在实际项目中,可以大量看到这种例子的存在。 591 | 592 | ## 8. Option转换 593 | 我们知道,在`Rust`中,需要使用到`unwrap()`的方法的对象有`Result`,`Option`对象。我们看下`Option`的大致结构: 594 | ```rust 595 | pub enum Option { 596 | /// No value 597 | #[stable(feature = "rust1", since = "1.0.0")] 598 | None, 599 | /// Some value `T` 600 | #[stable(feature = "rust1", since = "1.0.0")] 601 | Some(#[stable(feature = "rust1", since = "1.0.0")] T), 602 | } 603 | ``` 604 | `Option`本身是一个`enum`对象,如果该函数(方法)调用结果值没有值,返回`None`,反之有值返回`Some(T)` 605 | 606 | 如果我们想获取`Some(T)`中的`T`,最直接的方式是:`unwrap()`。我们前面说过,使用`unwrap()`的方式太过于暴力,如果出错,程序直接`panic`,这是我们最不愿意看到的结果。 607 | 608 | Ok,那么我们试想下, 利用`Option`能使用`?`语法糖吗?如果能用`?`转换的话,是不是代码结构就更简单了呢?我们尝试下,代码如下: 609 | 610 | ```rust 611 | 612 | #[derive(Debug)] 613 | enum Error { 614 | OptionError(String), 615 | } 616 | 617 | impl std::error::Error for Error {} 618 | 619 | impl std::fmt::Display for Error { 620 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 621 | match &self { 622 | Error::OptionError(ref e) => e.fmt(f), 623 | } 624 | } 625 | } 626 | 627 | pub type Result = std::result::Result; 628 | 629 | 630 | fn main() -> Result<()> { 631 | let bar = foo(60)?; 632 | assert_eq!("bar", bar); 633 | Ok(()) 634 | } 635 | 636 | fn foo(index: i32) -> Option { 637 | if index > 60 { 638 | return Some("bar".to_string()); 639 | } 640 | None 641 | } 642 | ``` 643 | 644 | 执行结果报错: 645 | ```bash 646 | error[E0277]: `?` couldn't convert the error to `Error` 647 | --> src/main.rs:22:22 648 | | 649 | 22 | let bar = foo(60)?; 650 | | ^ the trait `std::convert::From` is not implemented for `Error` 651 | | 652 | = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait 653 | = note: required by `std::convert::From::from` 654 | 655 | error: aborting due to previous error 656 | 657 | For more information about this error, try `rustc --explain E0277`. 658 | error: could not compile `hyper-define`. 659 | ``` 660 | 提示告诉我们没有转换`std::convert::From`,但是`NoneError`本身是`unstable`,这样我们没法通过`From`转换为**自定义Error**。 661 | 662 | 本身,在`Rust`的设计中,关于`Option`和`Result`就是一对孪生兄弟一样的存在,`Option`的存在可以忽略异常的细节,直接关注目标主体。当然,`Option`也可以通过内置的组合器`ok_or()`方法将其变成`Result`。我们大致看下实现细节: 663 | ```rust 664 | impl Option { 665 | pub fn ok_or(self, err: E) -> Result { 666 | match self { 667 | Some(v) => Ok(v), 668 | None => Err(err), 669 | } 670 | } 671 | } 672 | ``` 673 | 这里通过`ok_or()`方法通过接收一个**自定义Error**类型,将一个`Option`->`Result`。好的,变成`Result`的类型,我们就是我们熟悉的领域了,这样处理起来就很灵活。 674 | 675 | 关于`Option`的其他处理方式,不在此展开解决,详细的可看下面链接: 676 | 677 | 延伸链接:[https://stackoverflow.com/questions/59568278/why-does-the-operator-report-the-error-the-trait-bound-noneerror-error-is-no](https://stackoverflow.com/questions/59568278/why-does-the-operator-report-the-error-the-trait-bound-noneerror-error-is-no) 678 | 679 | ## 9. 避免unwrap() 680 | 有人肯定会有疑问,如果需要判断的逻辑,又不用`?`这种操作,怎么取出`Option`或`Result`的数据呢,当然点子总比办法多,我们来看下`Option`如何做的: 681 | ```rust 682 | fn main() { 683 | if let Some(v) = opt_val(60) { 684 | println!("{}", v); 685 | } 686 | } 687 | 688 | fn opt_val(num: i32) -> Option { 689 | if num >= 60 { 690 | return Some("foo bar".to_string()); 691 | } 692 | None 693 | } 694 | ``` 695 | 696 | 是的,我们使用`if let Some(v)`的方式取出值,当前`else`的逻辑就可能需要自己处理了。当然,`Option`可以这样做,`Result`也一定可以: 697 | 698 | ```rust 699 | fn main() { 700 | if let Ok(v) = read_file("./dat") { 701 | println!("{}", v); 702 | } 703 | } 704 | 705 | fn read_file(path: &str) -> Result { 706 | std::fs::read_to_string(path) 707 | } 708 | ``` 709 | 只不过,在处理`Result`的判断时,使用的是`if let Ok(v)`,这个和`Option`的`if let Some(v)`有所不同。 710 | 711 | 到这里,`unwrap()`的代码片在项目中应该可以规避了。补充下,这里强调了几次规避,就如前所言:**团队风格统一,方便管理代码,消除潜在危机**。 712 | 713 | ## 10. 自定义Error同级转换 714 | 我们在项目中,一个函数(方法)内部会有多次`Result`的结果判断:`?`,假设我们自定义的全局Error名称为:`GlobalError`。 715 | 716 | 这时候,如果全局有一个`Error`可能就会出现如下错误: 717 | 718 | ```rust 719 | std::convert::From>` is not implemented for `error::GlobalError 720 | ``` 721 | 722 | 意思是:我们自定义的`GlobalError`没有通过From>转换我们自己自定义的`GlobalError`,那这样,就等于**自己转换自己**。注意: 723 | 724 | * 第一:这是我们不期望这样做的。 725 | * 第二:遇到这种自己转换自己的`T`类型很多,我们不可能把出现的`T`类型通通实现一遍。 726 | 这时候,我们考虑自定义另一个Error了,假设我们视为:`InnnerError`,我们全局的Error取名为:`GlobalError`,我们在遇到上面错误时,返回`Result`,这样我们遇到`Result`时,只需要通过`From`转换即可,代码示例如下: 727 | 728 | ```rust 729 | impl From for GlobalError { 730 | fn from(s: InnerError) -> Self { 731 | Error::new(ErrorKind::InnerError(e)) 732 | } 733 | } 734 | ``` 735 | 736 | 上面说的这种情况,可能会在项目中出现**多个自定义Error**,出现这种情况时,存在多个不同Error的`std::result::Result`的返回。这里的`Err`就可以根据我们业务现状分别反回不同类型了。最终,只要实现了`From`的`trait`可转化为最终期望结果。 737 | 738 | ## 11. Error常见开源库 739 | 好了,介绍到这里,我们应该有了非常清晰的认知:关于如何处理`Rust`的错误处理问题了。但是想想上面的这些逻辑多数是模板代码,我们在实际中,大可不必这样。说到这里,开源社区也有了很多对错误处理库的支持,下面列举了一些: 740 | 741 | * [https://github.com/rust-lang-nursery/failure](https://github.com/rust-lang-nursery/failure) 742 | * [https://github.com/rust-lang-nursery/error-chain](https://github.com/rust-lang-nursery/error-chain) 743 | * [https://github.com/dtolnay/anyhow](https://github.com/dtolnay/anyhow) 744 | * [https://github.com/dtolnay/thiserror](https://github.com/dtolnay/thiserror) 745 | * [https://github.com/tailhook/quick-error](https://github.com/tailhook/quick-error) 746 | 747 | 748 | ## 12. 参考链接 749 | * [https://blog.burntsushi.net/rust-error-handling/](https://blog.burntsushi.net/rust-error-handling/) 750 | * [https://doc.rust-lang.org/edition-guide/rust-2018/error-handling-and-panics/question-mark-in-main-and-tests.html](https://doc.rust-lang.org/edition-guide/rust-2018/error-handling-and-panics/question-mark-in-main-and-tests.html) 751 | * [https://doc.rust-lang.org/rust-by-example/error/result.html](https://doc.rust-lang.org/rust-by-example/error/result.html) 752 | * [https://doc.rust-lang.org/rust-by-example/error.html](https://doc.rust-lang.org/rust-by-example/error.html) 753 | * [https://github.com/rust-lang/rust/issues/43301](https://github.com/rust-lang/rust/issues/43301) 754 | 755 | ## 13 错误处理实战 756 | 这个例子介绍了如何在`https://github.com/Geal/nom`中处理错误,这里就不展开介绍了,有兴趣的可自行阅读代码。 757 | 758 | 详细见链接:[https://github.com/baoyachi/rust-error-handle/blob/master/src/demo_nom_error_handle.rs](https://github.com/baoyachi/rust-error-handle/blob/master/src/demo_nom_error_handle.rs) 759 | 760 | ## 14. 总结 761 | 好了,经过上面的长篇大论,不知道大家是否明白如何自定义处理Error呢了。大家现在带着之前的已有的问题或困惑,赶紧实战下`Rust`的错误处理吧,大家有疑问或者问题都可以留言我,希望这篇文章对你有帮助。 762 | 763 | 文中代码详见:[https://github.com/baoyachi/rust-handle-error/tree/master/src](https://github.com/baoyachi/rust-handle-error/tree/master/src) 764 | 765 | 原文地址:[https://github.com/baoyachi/rust-error-handle](https://github.com/baoyachi/rust-error-handle) 766 | --------------------------------------------------------------------------------