Зависит от того, как именно сэкономить. Растовский подход (как и Гошный) позволяет прямо по сигнатуре функции понять, что она может вернуть ошибку. Что позволяет в вызывающем коде понять, есть только один путь исполнения этой функции или два.
Я пытаюсь сказать, что если синтаксис try-pattern не слишком бойлерплейтный (как это сделано в расте), то вариант (а) не имеет очевидных недостатков по сравнению с (г). И выбирать, в случае раста, стоит именно его (а не panic/unwind, который и не рекомендуется для управления потоком выполнения).
Так (а) от (г) отличается только явностью контракта.
Контракт (а) - каждая из промежуточных функций знает, что её вызов может упасть и она должна пробросить результат выше (что, в некоторых языках, весьма многословно, а в других гораздо менее), при этом, возможно, поменяв конкретный тип ошибки. Контракт (г) - ни одна из промежуточных функций не имеет право ловить тот эксепшен, который должен пройти сквозь всю цепочку вызовов.
Это зависит от параметров компиляции. Есть возможность скомпилировать так, чтобы panic приводил к завершению программы без unwind стэка, в таком случае поймать unwind не представляется возможным.
строим граф зависимостей и приходим к выводу, что foo тоже может.
Если это делает компилятор, то эту ошибку нельзя нигде выше по стеку проверить (потому что нужно запрещать проверять код ошибок функций, которые точно их не выбрасывают). Обсуждалось вот в этом https://habr.com/en/post/709328/comments/#comment_25117572 сообщении.
Если это делает человек, то где выигрыш?
Если E - контейнер, то он выделяется на куче только если что-то случится (и это в любом случае нужно выделять, вне зависимости от парадигмы, если мы хотим эту большую информацию получать при обработке). А простые структуры можно продолжать хранить на стеке (и раст продолжает).
Я не спорю, что гошная очень явная обработка на всех 8 уровнях - это больно. Более того, авторы языка это тоже понимают и предлагают второй механизм для этого (panic/recover).
Растовская гораздо менее многословна, а ещё, в качестве бонуса, позволяет блоки except MyDetailedError e {throw new MyGneralError(e)} написать один раз для каждой пары Detailed/General, а дальше указывать только типы, такая конвертация будет случаться под капотом.
Так выбрасывать exception на восемь уровней вверх - это же плохая практика: - во-первых, легко случайно начать ловить его по дороге в рамках рефакторинга кода - во-вторых, он, вероятно, будет не того уровня абстракции.
Так что Result, что result, error - это ручная разметка, а не что-то автоматическое. Если эта разметка уже делается руками, то Result выглядит хорошим решением - нельзя взять результат не проверив код возврата.
Можно подробнее что предлагается сделать? Всегда проверять "код возврата"? Даже у функций, которые гарантированно не падают? Это будет дороже по перформансу.
В случае, если оба типа в нём указатели, то про него можно думать как про код возврата (вернулся результат или ошибка) + указатель на соответствующий объект (в первом случае на результат, во втором на ошибку). И весь ? или if let - это проверка кода возврата.
Result - это не джавовский объект, под который нужно выделять много памяти, это просто enum с двумя возможными значениями. При этом, если хочется, его можно менять прямо на месте (и, в достаточно большом классе случаев, компилятор это даже за тебя делает).
Так на питоне только сам лист + push + RemoveNode (которого нет в rust имплементации).
Если взять лист + push, которые есть в обоих, то разница будет только в ссылочной прозрачности (drop на самом деле не необходим на расте, см https://play.rust-lang.org/?version=stable&mode=debug&edition=2015&gist=e8dfac514684d1a17ddd307b90e2f281 разница с питоном минимальная.
Единственное, о чём нужно задумываться в рамках аналога RemoveNonde - это о прередаче владения при удалении ноды (и внимательно следить где ссылки, а где объекты).
Можно сделать, например, вот так: https://play.rust-lang.org/?version=stable&mode=debug&edition=2015&gist=2d2c3f5e8b1a5d8495a3f7220bec4285
Не сказал бы, что это сильно длиннее.
Компилятор Раста довольно часто заменяет копирование на перемещение, если "скопированный" объект больше не используется.
Зависит от того, как именно сэкономить. Растовский подход (как и Гошный) позволяет прямо по сигнатуре функции понять, что она может вернуть ошибку. Что позволяет в вызывающем коде понять, есть только один путь исполнения этой функции или два.
Я пытаюсь сказать, что если синтаксис try-pattern не слишком бойлерплейтный (как это сделано в расте), то вариант (а) не имеет очевидных недостатков по сравнению с (г). И выбирать, в случае раста, стоит именно его (а не panic/unwind, который и не рекомендуется для управления потоком выполнения).
Так (а) от (г) отличается только явностью контракта.
Контракт (а) - каждая из промежуточных функций знает, что её вызов может упасть и она должна пробросить результат выше (что, в некоторых языках, весьма многословно, а в других гораздо менее), при этом, возможно, поменяв конкретный тип ошибки.
Контракт (г) - ни одна из промежуточных функций не имеет право ловить тот эксепшен, который должен пройти сквозь всю цепочку вызовов.
Это зависит от параметров компиляции. Есть возможность скомпилировать так, чтобы panic приводил к завершению программы без unwind стэка, в таком случае поймать unwind не представляется возможным.
Если это делает компилятор, то эту ошибку нельзя нигде выше по стеку проверить (потому что нужно запрещать проверять код ошибок функций, которые точно их не выбрасывают). Обсуждалось вот в этом https://habr.com/en/post/709328/comments/#comment_25117572 сообщении.
Если это делает человек, то где выигрыш?
Если E - контейнер, то он выделяется на куче только если что-то случится (и это в любом случае нужно выделять, вне зависимости от парадигмы, если мы хотим эту большую информацию получать при обработке). А простые структуры можно продолжать хранить на стеке (и раст продолжает).
Да, более того, уже есть, например, anyhow / thiserror, любому из которых можно делегировать весь бойлерплейт в добавлении сообщений в стэктрейс.
Я не спорю, что гошная очень явная обработка на всех 8 уровнях - это больно. Более того, авторы языка это тоже понимают и предлагают второй механизм для этого (panic/recover).
Растовская гораздо менее многословна, а ещё, в качестве бонуса, позволяет блоки except MyDetailedError e {throw new MyGneralError(e)} написать один раз для каждой пары Detailed/General, а дальше указывать только типы, такая конвертация будет случаться под капотом.
Так выбрасывать exception на восемь уровней вверх - это же плохая практика:
- во-первых, легко случайно начать ловить его по дороге в рамках рефакторинга кода
- во-вторых, он, вероятно, будет не того уровня абстракции.
Нет, конечно. Есть возможность пробросить ошибку выше.
Ещё одна проблема try-паттерна в том, что есть возможность обратиться к out даже если функция завершилась неуспехом.
Как предлагается узнавать, нужно ли читать эту централизованную переменную?
Если это общий код возврата, то почему Вы считаете, что переменная в куче будет быстрее, чем на стеке?
Чем то одна, то две переменных на стеке хуже, чем всегда две, как Вы предлагаете?
Так exception и в C++ не делает longjump.
Так что Result, что result, error - это ручная разметка, а не что-то автоматическое. Если эта разметка уже делается руками, то Result выглядит хорошим решением - нельзя взять результат не проверив код возврата.
Именно, что обсуждалось.
Компилятору нужна разметка, чтобы знать, где проверять (см. noexcept).
Можно подробнее что предлагается сделать?
Всегда проверять "код возврата"? Даже у функций, которые гарантированно не падают? Это будет дороже по перформансу.
В случае, если оба типа в нём указатели, то про него можно думать как про код возврата (вернулся результат или ошибка) + указатель на соответствующий объект (в первом случае на результат, во втором на ошибку). И весь ? или if let - это проверка кода возврата.
Result - это не джавовский объект, под который нужно выделять много памяти, это просто enum с двумя возможными значениями. При этом, если хочется, его можно менять прямо на месте (и, в достаточно большом классе случаев, компилятор это даже за тебя делает).