Я начал изучать Rust на прошлой неделе, читая книги и статьи и одновременно пытаясь конвертировать код из других языков.
Я столкнулся с ситуацией, которую я пытаюсь проиллюстрировать с помощью приведенного ниже кода (который представляет собой упрощенную версию того, что я пытался преобразовать из другого языка):
#[derive(Debug)]
struct InvalidStringSize;
impl std::fmt::Display for InvalidStringSize {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "string is too short")
}
}
impl std::error::Error for InvalidStringSize {}
pub fn extract_codes_as_ints(
message: String,
) -> Result<(i32, i32, i32), Box<dyn std::error::Error>> {
if message.len() < 20 {
return Err(Box::new(InvalidStringSize {}));
}
let code1: i32 = message[0..3].trim().parse()?;
let code2: i32 = message[9..14].trim().parse()?;
let code3: i32 = message[17..20].trim().parse()?;
Ok((code1, code2, code3))
}
Итак, в основном я хочу извлечь 3 целых числа из определенных позиций заданной строки (я также мог бы попытаться проверить другие символы на наличие некоторых шаблонов, но я пропустил эту часть).
Мне было интересно, есть ли способ «поймать» или проверить все три результата вызовов синтаксического анализа одновременно? Я не хочу добавлять блок соответствия для каждого, я просто хотел бы проверить, не привел ли кто-нибудь к ошибке, и в этом случае вернуть другую ошибку. Имеет смысл?
Единственное решение, которое я мог придумать до сих пор, - это создать другую функцию со всеми анализами и сопоставить ее результат. Есть ли другой способ сделать это?
Кроме того, любые отзывы/предложения по другим частям кода очень приветствуются, я изо всех сил пытаюсь найти «правильный способ» делать что-то в Rust.
@ChayimFriedman Насколько я понимаю, OP хочет поймать ошибку синтаксического анализа и в этом случае вернуть ошибку другого типа.
Да, именно так, как сказал @Lagerbaer, извините, если я не ясно выразился. Другой возможностью было бы добавить некоторый код обработки ошибок, например, ведение журнала или что-то еще.
Идиоматический способ добиться этого — определить свой собственный тип ошибки и вернуть его с From<T>
реализацией для каждого типа ошибки T
, который может возникнуть в вашей функции. Оператор ?
выполнит .into()
преобразования, чтобы соответствовать типу ошибки, который объявлена вашей функцией.
Коробочная ошибка здесь излишня; просто объявите перечисление со списком всех способов, которыми функция может дать сбой. Вариант для целочисленной ошибки синтаксического анализа может даже зафиксировать пойманную ошибку.
use std::fmt::{Display, Formatter, Error as FmtError};
use std::error::Error;
use std::num::ParseIntError;
#[derive(Debug, Clone)]
pub enum ExtractCodeError {
InvalidStringSize,
InvalidInteger(ParseIntError),
}
impl From<ParseIntError> for ExtractCodeError {
fn from(e: ParseIntError) -> Self {
Self::InvalidInteger(e)
}
}
impl Error for ExtractCodeError {}
impl Display for ExtractCodeError {
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
match self {
Self::InvalidStringSize => write!(f, "string is too short"),
Self::InvalidInteger(e) => write!(f, "invalid integer: {}", e)
}
}
}
Теперь нам просто нужно изменить тип возвращаемого значения вашей функции и заставить его возвращать ExtractCodeError::InvalidStringSize
, когда длина слишком мала. Больше ничего менять не нужно, так как ParseIntError
автоматически преобразуется в ExtractCodeError
:
pub fn extract_codes_as_ints(
message: String,
) -> Result<(i32, i32, i32), ExtractCodeError> {
if message.len() < 20 {
return Err(ExtractCodeError::InvalidStringSize);
}
let code1: i32 = message[0..3].trim().parse()?;
let code2: i32 = message[9..14].trim().parse()?;
let code3: i32 = message[17..20].trim().parse()?;
Ok((code1, code2, code3))
}
В качестве дополнительного бонуса вызывающие функции смогут легче проверять ошибки, чем с помощью dyn Error
в рамке.
В более сложных случаях, например, когда вы хотите немного изменить ошибку для каждого возможного появления ParseIntError
, вы можете использовать .map_err()
в результатах для преобразования ошибки. Например:
something_that_can_fail.map_err(|e| SomeOtherError::Foo(e))?;
Я не совсем понимаю, чего вы хотите: вы уже ошибаетесь, когда число не поддается разбору, не так ли?