LINUX.ORG.RU

Как удобно обрабатывать ошибки в rust'е без RUST_BACKTRACE=1?

 , ,


0

3

Тыкаю руст, вот дошел до момента, когда хочется удобно залогать ошибку, которая выпала где-то внутри.

Примерный код:

fn main() {
    let args: Vec<String> = ...
    let res = doSmth(args);
    match res {
        Ok(val) => println!("Result: {}", val),
        Err(err) => println!("Error: {}", err)
    }
}

fn doSmth(args: Vec<String>) -> Result<String, ParseError> {
    let v1 = parse(args[0]);
    ...
    let v2 = parse(args[1]);
    ...
    let v3 = parse(args[2]);
    ...
}

fn parse(v: String) -> Result<String, ParseError> {
...
}

Вот это всё прекрасно работает но я получаю только ParseError без уточнения места где оно выпало. Поскольку у меня в коде 3 точки где происходит парсинг то это может быть любая из них и как потом это отлаживать?

Есть вариант упасть в панику сразу, но чтобы всё было красиво нужен RUST_BACKTRACE=1 да и падать на месте совсем не хочется, хочется человеческий еррор.

На данный момент наклепал макрос, который создает кастомный еррор используя file!() и line!() и ловлю его.

Что-то вроде

fn parse(v: String) -> Result<String, MySuperPuperError> {
...
    if !parsed {
        throw!("Parsing Error");
    }
}

fn doSmth(args: Vec<String>) -> Result<String, MySuperPuperError> {
... 
    let v1 = catch!(parse(args[0]));
    let v2 = catch!(parse(args[1]));
    let v3 = catch!(parse(args[2]));
... 
} 

fn main() {
...
        Err(err) => println!("Error: {} at {}", err.message, err.position)
...
}

Я предполагаю, что кто-то уже должен был придумать что-то подобное, но я пока не нашел ничего чем было бы удобно пользоваться. Видел error_chain но он всё равно какой-то неудобный.

★★★★★

Последнее исправление: ya-betmen (всего исправлений: 1)

В идеале ты делаешь свой кастомный enum ошибок и указываешь там все возможные проблемы (используй thiserror). Альтернативно делаешь .map_err в месте где происходит ошибка и оттуда печатаешь в лог сообщение.

pftBest ★★★★
()
Последнее исправление: pftBest (всего исправлений: 1)

Не пишу на Расте, но нагуглил вот такое решение, похожее на твоё с вызовом макросов file! и line!.

Вот тут ещё говорят, что thiserror (одна из самых популярных библиотек для ошибок, насколько я понимаю), позволяет сохранять бэктрейс внутри ошибки.

Видел error_chain но он всё равно какой-то неудобный.

И он ещё заброшен пару лет уже как.

theNamelessOne ★★★★★
()
Ответ на: комментарий от theNamelessOne

Бэктрейсы не работают сейчас. Они раньше работали на nightly через нестабильные фичи которые сейчас активно переделывают чтобы стабилизировать наконец. Поэтому придется подождать пару релизов.

pftBest ★★★★
()
Ответ на: комментарий от theNamelessOne

Мой ответ показывает как правильно сделать ошибки с красивыми сообщениями, что рекомендую в серьезных проектах. Если просто надо файл и строку ошибки показать то достаточно dbg! макроса, например

    let v1: i32 = parse(&args[0]).map_err(|e| dbg!(e))?;
    let v2: i32 = parse(&args[1]).map_err(|e| dbg!(e))?;
    let v3: i32 = parse(&args[2]).map_err(|e| dbg!(e))?;

пример на playground:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&am...

pftBest ★★★★
()
Ответ на: комментарий от pftBest

Если просто надо файл и строку ошибки показать то достаточно dbg! макроса

Мне кажется, что такое работает хорошо только в игрушечном примере. Представь, что у тебя большой проект, где ошибка может возникнуть в куче разных файлов, ты же не будешь все такие места искать вручную и прописывать для каждого .map_err(|e| dbg!(e)) (да и вряд ли ты потащишь тот же dbg! в прод).

theNamelessOne ★★★★★
()
Ответ на: комментарий от theNamelessOne

Правильно, в нормальном проекте будет enum в котором все возможные ошибки перечислены, и сразу понятно что пошло не так, и найти место в исходнике можно просто поиском по типу ошибки.

Если проект совсем большой то можно сделать иерархию енумов ошибок с автоматической конверсией через #[from]

pftBest ★★★★
()
Последнее исправление: pftBest (всего исправлений: 1)
Ответ на: комментарий от pftBest

указываешь там все возможные проблемы

Чтобы в моем примере это сработало мне придется так завернуть каждый из трех вызовов метода parse чтобы иметь возможность понять откуда прилетела проблема, с логированием ошибки такая же шляпа, либо я втыкаю лог вокруг каждого вызова (это припец бойлерплейт) либо хз парсинг чего именно отпал.

ya-betmen ★★★★★
() автор топика
Ответ на: комментарий от pftBest

Правильно, в нормальном проекте будет enum в котором все возможные ошибки перечислены

Проблема не в том чтобы завести по ошибке на каждый чих, а в том чтобы понять кто чихнул.

ya-betmen ★★★★★
() автор топика
Ответ на: комментарий от ya-betmen

Проблема не в том чтобы завести по ошибке на каждый чих, а в том чтобы понять кто чихнул.

Ничего не поделаешь, бектрейсы еще не стабилизировали. Ну и в расте не принято бектрейсы для обычных ошибок, так же как и в Go. Если потрудишься и заведешь по разной ошибке на каждый чих то проблема откуда конкретная вылетела решится автоматическию.

Для того чтобы меньше бойлерплейта в коде было посмотри на #[from]. Идея в том что когда ты делаешь оператор ? то тип ошибки не обязательно должен быть такой же как и выпал, оно вызовет метод .into() само для конверсии. Просто в твоем примере все через один метод parse было поэтому не продемонстрируешь эту фичу.

pftBest ★★★★
()
Последнее исправление: pftBest (всего исправлений: 1)
Ответ на: комментарий от pftBest

Просто в твоем примере все через один метод parse было поэтому не продемонстрируешь эту фичу

Так я и ищу способ решения моей проблемы, а не способ заюзать фичу запиленную авторами языка. Если например у меня есть метод, который парсит строку в мой тип данных и я дергаю его из трех мест (да я слишком ленивый чтобы писать три разных метода) то вопросик бесполезен.

ya-betmen ★★★★★
() автор топика
Ответ на: комментарий от zurg

показывает и файл и строку, и вообще произвольные параметры, чё-то не совсем понятно что именно надо

Надо понять что именно сломалось. Ну посмотри ОП.

Например есть метод parse он может нираспарсить кривые данные. Вызавается этот метод из трех разных мест. Assert покажет на метод parse, а мне нужен метод, который его дергал.

ya-betmen ★★★★★
() автор топика
Ответ на: комментарий от ya-betmen

ну если вызываешь из 3 разных мест, то собственно и модифицировать ошибку нужно в этих трех местах. Неужели так сложно .map_err написать 3 раза? Если бы писал на православной сишке или плюсах то точно такая же проблема бы была и такое же решение.

pftBest ★★★★
()
Ответ на: комментарий от pftBest

Вот сократил количество кода еще сильнее, уже короче сложно придумать

    let v1: i32 = parse(&args[0]).map_err(MyParseError::Problem1)?;
    let v2: i32 = parse(&args[1]).map_err(MyParseError::Problem2)?;
    let v3: i32 = parse(&args[2]).map_err(MyParseError::Problem3)?;

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&am...

pftBest ★★★★
()
Последнее исправление: pftBest (всего исправлений: 1)
Ответ на: комментарий от pftBest

Хм, у тебя действительно выглядит норм. Почему-то у меня как-то длиннее получалось, надо на реальном коде попробовать ещё раз. Спасибо.

ya-betmen ★★★★★
() автор топика