Обработка ошибок
Правильная обработка ошибки в main функции
Обрабатывает ошибку, возникающую при попытке открыть файл, который не существует. Это достигается с помощью библиотеки error-chain, которая заботится об автоматической генерации большого количества стандартного кода, необходимого для обработки ошибок в Rust .
Io(std::io::Error)
внутри foreign_links
позволяет автоматическое преобразование из std::io::Error
в объявленный тип в error_chain!
, реализующий типаж Error
.
Приведённый ниже рецепт расскажет, как долго работает система, открыв файл Unix /proc/uptime
и проанализировав его содержимое, получив первое число. Он возвращает время безотказной работы, если нет ошибки.
Другие рецепты в этой книге скрывают сгенерированный код error-chain, его можно увидеть, развернув код с помощью кнопки ⤢.
#[macro_use] extern crate error_chain; use std::fs::File; use std::io::Read; error_chain!{ foreign_links { Io(std::io::Error); ParseInt(::std::num::ParseIntError); } } fn read_uptime() -> Result<u64> { let mut uptime = String::new(); File::open("/proc/uptime")?.read_to_string(&mut uptime)?; Ok(uptime .split('.') .next() .ok_or("Cannot parse uptime data")? .parse()?) } fn main() { match read_uptime() { Ok(uptime) => println!("uptime: {} seconds", uptime), Err(err) => eprintln!("error: {}", err), }; }
Избегайте отбрасывания ошибок во время их преобразований
Крейт error-chain делает сопоставление с различными типами ошибок, возвращаемых функцией, возможным и относительно компактным. ErrorKind
определяет тип ошибки.
Используется библиотека reqwest для запроса веб-службы генератора случайных целых чисел. Преобразует строковый ответ в целое число. Стандартная библиотека Rust, библиотека reqwest и веб-сервис могут вернуть ошибки. Хорошо определённые ошибки Rust используют foreign_links
. Дополнительный вариант ErrorKind
для ошибки веб-службы использует блок errors
в error_chain!
макросе.
#[macro_use] extern crate error_chain; extern crate reqwest; use std::io::Read; error_chain! { foreign_links { Io(std::io::Error); Reqwest(reqwest::Error); ParseIntError(std::num::ParseIntError); } errors { RandomResponseError(t: String) } } fn parse_response(mut response: reqwest::Response) -> Result<u32> { let mut body = String::new(); response.read_to_string(&mut body)?; body.pop(); body.parse::<u32>() .chain_err(|| ErrorKind::RandomResponseError(body)) } fn run() -> Result<()> { let url = format!("https://www.random.org/integers/?num=1&min=0&max=10&col=1&base=10&format=plain"); let response = reqwest::get(&url)?; let random_value: u32 = parse_response(response)?; println!("a random number between 0 and 10: {}", random_value); Ok(()) } fn main() { if let Err(error) = run() { match *error.kind() { ErrorKind::Io(_) => println!("Standard IO error: {:?}", error), ErrorKind::Reqwest(_) => println!("Reqwest error: {:?}", error), ErrorKind::ParseIntError(_) => println!("Standard parse int error: {:?}", error), ErrorKind::RandomResponseError(_) => println!("User defined error: {:?}", error), _ => println!("Other error: {:?}", error), } } }
Получение трассировки (backtrace) сложных сценариев ошибок
Этот рецепт показывает, как обрабатывать сложный сценарий ошибки, а затем напечатать обратную трассировку. Он полагается на chain_err
для расширения стека ошибок путём добавления новых ошибок. Стек ошибок можно развернуть, что обеспечивает лучший контекст для понимания причины возникновения ошибки.
Приведённые ниже рецепты пытаются десериализовать значение 256
в тип u8
. Ошибка всплывёт из Serde, затем csv и наконец к коду пользователя.
extern crate csv; #[macro_use] extern crate error_chain; #[macro_use] extern crate serde_derive; use std::fmt; error_chain! { foreign_links { Reader(csv::Error); } } #[derive(Debug, Deserialize)] struct Rgb { red: u8, blue: u8, green: u8, } impl Rgb { fn from_reader(csv_data: &[u8]) -> Result<Rgb> { let color: Rgb = csv::Reader::from_reader(csv_data) .deserialize() .nth(0) .ok_or("Cannot deserialize the first CSV record")? .chain_err(|| "Cannot deserialize RGB color")?; Ok(color) } } impl fmt::UpperHex for Rgb { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let hexa = u32::from(self.red) << 16 | u32::from(self.blue) << 8 | u32::from(self.green); write!(f, "{:X}", hexa) } } fn run() -> Result<()> { let csv = "red,blue,green 102,256,204"; let rgb = Rgb::from_reader(csv.as_bytes()).chain_err(|| "Cannot read CSV data")?; println!("{:?} to hexadecimal #{:X}", rgb, rgb); Ok(()) } fn main() { if let Err(ref errors) = run() { eprintln!("Error level - description"); errors .iter() .enumerate() .for_each(|(index, error)| eprintln!("└> {} - {}", index, error)); if let Some(backtrace) = errors.backtrace() { eprintln!("{:?}", backtrace); } // In a real use case, errors should handled. For example: // ::std::process::exit(1); } }
Отображение трассировки ошибки:
Error level - description
└> 0 - Cannot read CSV data
└> 1 - Cannot deserialize RGB color
└> 2 - CSV deserialize error: record 1 (line: 2, byte: 15): field 1: number too large to fit in target type
└> 3 - field 1: number too large to fit in target type
Запустите рецепт с RUST_BACKTRACE=1
чтобы отобразить подробную трассировку backtrace
связанную с этой ошибкой.