Здравствуйте, vsb, Вы писали:
_>>Твоя фраза подразумевает, что при обработке с исключениями не надо писать какой-то код, который надо писать при обработке ошибок через Result в Rust'e. И т.к. это очевидно не код самой реакции на ошибку (который естественно надо писать в любом подходе и языке), то тогда о чём собственно речь? Или ты считаешь "дополнительным кодом" несколько знаков вопроса?
vsb>Ну смотри. Может я в расте что-то не понимаю.
vsb>Во-первых у каждой функции с ошибками надо возвращать Result вместо конкретного типа. Это как минимум синтаксический шум.
Это ясное выражение в коде того факта, что функция может сгенерировать ошибку. Ведь далеко не все функции такие, есть множество с гарантированным выполнением. Хотя в языке с исключениями об этом факте сложно судить.
vsb>Во-вторых если функция сейчас не бросает ошибок, а завтра начинает бросать, мне надо менять её тип, потом проходить по всем местам её использования и как минимум добавлять туда этот самый вопросик. Потенциально надо рекурсивно менять тип этой вызывающий функции и так далее.
Это как раз максимально удобно, потому что тут тебе IDE сама покажет место, куда необходимо вставить обработчик ошибок. Ведь для ошибок (а не критических ситуаций, которые обрабатываются паниками) как раз характерна обработка по месту, а не один глобальный обработчик на всё приложение.
vsb>В-третьих — да, вопрос это дополнительный код с не такой уж и тривиальной логикой. И один символ тут не должен вводить в заблуждение.
Если ты имеешь в виду ассемблер, то как бы он и в языках с исключениями имеется (слышал про про формирование фрейма исключений?), просто его вставляет компилятор автоматически. И кстати это одна из известных проблем оптимизации, в том смысле что в большинстве языков нет способа сказать компилятору что данная функция точно ничего не бросает (и соответственно тут можно многое оптимизировать). В C++ для этого даже пришлось вводить отдельное ключевое слово! )
vsb>В-четвёртых можно пример, как будет выглядеть код аналогичный
vsb>vsb>List<Integer> parseStrings(List<String> strings) {
vsb> return strings.stream().map(s -> Integer.parseInt(s)).toList();
vsb>}
vsb>
vsb>есть у меня подозрение, что вопросиком тут не отделаешься. Конечно на выходе нужен именно результат над списком чисел, а не список результатов над числами.
На это тебе уже тут подробно ответили. )
_>>Именно! И как раз для таких ситуаций исключения (или паники) подходят идеально.
vsb>Насчёт исключений — хз. В жаве есть какой-то флажок, который при первом же OOM-е просто завершает процесс вместо выбрасывания исключения (и вроде хипдамп пишет). Вот это правильный подход и если бы мне не было лень это прописывать, я бы его прописывал.
Это и есть образцовая паника. )))
_>>Ну тут есть довольно простой ответ, через встречный вопрос. ))) Вот допустим ты используешь механизм исключений для обработки обычных ошибок. Можешь ли ты взяв произвольную функцию в коде точно перечислить список ошибок, который она может вернуть?
vsb>Нет, конечно (ну если не считать throws, который был плохой идеей).
vsb>Но зачем? Всё, что я знаю — что он может кинуть Exception. Если мне нужно что-то более конкретное — я пойду в нужное место и посмотрю.
В какое такое нужное место? В документацию на функцию из библиотеки? Ну допустим там могут (и то далеко не всегда такое можно увидеть в описание функции) перечислить свои родные исключения. А они точно перечислят ещё и все возможные исключения чужого кода (включая стандартную библиотеку), вызовы которого есть внутри вызова их функции?
vsb>В Java придумали throws в своё время который как раз позволяет типы выбрасываемых исключений добавлять в сигнатуру функции. Но это оказалось плохой идеей и сейчас эту фичу стараются избегать.
Ну вот ты можешь рассматривать сигнатуру функции с Result в Rust, как функцию в Java с использование throws. Но есть два отличающихся нюанса, которые полностью меняют всю картину:
1. В Java данная фича является опциональной, а не обязательной что на корню режет идею о любых гарантиях. Т.е. в таком виде это действительно бесполезное действо, только отнимающее время.
2. В Java нет требования сводить все исключения, бросаемые функцией, к одному типу (пусть даже и максимально абстрактному базовому типу), так что в итоге оператор throws мог выглядеть ужасно (огромным списком из разных иерархий). В Rust'е же это всегда один тип (хотя это может быть и спец. контейнер, который может хранить произвольные типы других ошибок), так что всегда имеет лаконичный код и удобную обработку (через сопоставление с образцом).