Да, это потребует эмуляции АТД. Но без short-circuit return в выражениях оно будет выглядеть убого. Довольно забавно, что в С++, при всей необязательности исключений (а иногда и невозможности), до сих пор нет идиоматичного способа обработки ошибок без их участия.
На всякий случай уточняю, что описанная ерунда с раскруткой цепочки призвана применяться в месте, где ошибка показывается в окошке пользователю, а не повсеместно в коде.
А можно пример того, что вы пытались сделать и что получилось? Откуда у вас эти try-catch появляются?
Если коротко, есть некий проект где вся обработка ошибок строится на возврате true/false либо пустого/непустого объекта. Я пытаюсь подобрать схему обработки ошибок на базе цепочки исключений, построенной через http://en.cppreference.com/w/cpp/error/nested_exception. Проблема — код для прохода по этой цепочке выглядит откровенно криво:
void ForEachNestedException(std::exception_ptr ex, FunctionRef<void (FunctionRef<void()>)> callback)
{
while(ex != nullptr)
{
std::exception_ptr next = nullptr;
try
{
std::rethrow_exception(ex);
}
catch(std::nested_exception& e)
{
next = e.nested_ptr();
}
catch(...) {} // Silence for now
try // In case callback won't swallow exception
{
callback([&] () { std::rethrow_exception(ex); });
}
catch(...) {}
ex = next;
}
}
FunctionRef здесь — аналог std::function, который только ссылается на callable.
Далее пользовательский код в месте отлова будет выглядеть как-то так:
void main()
{
try
{
// ... some useful code
}
catch(...)
{
ForEachNestedException(std::current_exception(), [&] (FunctionRef<void()> rethrow)
{
try
{
rethrow();
}
catch(ContextError& e)
{
// Process first type
}
catch(AppError& e)
{
// Process second type
}
catch(std::exception& e)
{
// Process all c++ exceptions
}
catch(...)
{
// Process leftovers
}
})
}
}
Собственно, выходит минимум 2 throw на исключение. Пока ничего лучше я не придумал.
Альтернатива — делать свой NestedException.
В очередной раз попытался реализовать обработку ошибок через исключения. Поохал от кол-ва try-catch при размотке nested_exception. В связи с noexcept и неоднозначным отношением к исключениям, возникла дурацкая мысль. Что если return станет выражением? Тогда (псевдокод конечно):
Дело именно в этом. Семантически такой "пустой объект" — всё ещё объект. К нему можно получить доступ, дёргать методы и т.п. Более того, С++ позволяет легко сделать ссылку-перемещение из простой мутабельной ссылки. В ряде случаев это может приводить к, например, неконтроллируемому появлению ссылок на элементы вектора, к примеру, которых уже давно нет на этом месте. Потому что исходный вектор был мало того что перемещён, а вдобавок реаллоцирован. В Rust такой перемещённый объект становится недоступен в принципе.
В Rust одна семантика.
Что конфликтует с лайфтаймами в С++ — принцип "копирование первично". Т.е. даже после перемещения объекта на его месте остаётся "заглушка" в неопределённом, пусть и валидном состоянии.Проконтролировать такое можно в простых случаях, с менее простыми будут проблемы.
Вполне возможно, вы найдёте ответ на свой вопрос по этой ссылке: https://github.com/redox-os/redox
Да, это пока исследовательский проект. Но эта ОС вполне себе работает. Ядро написано на голом Rust.
Не стоит, это правда. Но новички будут ошибаться просто от незнания инструмента. В список таких ошибок входит и неправильный дизайн. Но мы вроде говорим об инженерах, т.е. людях, которые хотя бы главу про unsafe прочтут в доке.
Неправильная формулировка с моей стороны. Правильная — "В Rust небезопасное поведение локализовано в unsafe блоках"
Хм, я вроде нигде не говорил, что unsafe сам магически локализуется.
Да. Согласен. Если код пишет идиот, который считает что компилятор/интерпретатор мешает его творческому полёту мысли, он может написать шлак на чём угодно, начиная с Asm и заканчивая Agda/Idris. Впрочем, до последних он скорее всего не дойдёт.
Значит я вас таки неправильно понял. Отвечаю на поставленный вопрос
Ответьте мне пожалуйства на следущий вопрос — может быть я правда чего-то не знаю о Rust. Каким образом Rust позволяет локализовать unsafe блоки в коде и не допустить чтобы они разбредались по всей кодовой базе?
Если коротко — никак. Нет там магии и единорогов. Там есть возможность проверить на границе "безопасности" инварианты и получить дополнительные гарантии, помощь компилятора и проверки внутри безопасной зоны. Но ничто не помешает вам растянуть "границу безопасности" на весь код. Также как в ООП-языке ничто не помешает вам использовать паттерн "Паблик Морозов".
Я, кстати, не видел пока ни одного языка, следящего за дизайном приложения. Есть линтеры — но не встроенные в язык.
Вы утверждаете, что Rust ничем не лучше С++, т.к. точно также позволяет при определённом желании наговнокодить, потоптаться по памяти и сделать другие плохие вещи. Более того, он с важей т.з. хуже т.к. накладывает дополнительные ограничения. Также он хуже, поскольку про него меньше статей, best practices etc. Ещё вам, похоже, надоели евангелисты, кричащие на всех углах, как Rust безопасен. Могу понять. Но главное пожалуй то, что для него гораздо меньше наработанного кода, библиотек и поддержки платформ. Если я где-то ошибся, поправьте.
Теперь попробую ответить на ваше утверждение, приведённое в формулировке выше.
Да, безусловно Rust позволяет сделать какую-нибудь бяку. Потому что язык для системного программирования смотрится мягко говоря странно без возможности прямой работы с памятью. Проблема немного не в том. В С++ требуются определённые усилия чтобы не допустить ошибку. Например, следить, куда и как передаём указатели и ссылки, вовремя смотреть, не "утечёт" ли указатель из вызванной функции в какую-нибудь глобальную переменную и т.п. Да, в тривиальных случаях это не проблема. Но кроме них есть нетривиальные случаи. В Rust при этом дополнительные сознательные усилия требуются чтобы написать неправильно — например, обозначить блок как unsafe, и в нём работать с указателями напрямую. Потому что ссылки даже внутри unsafe проверяются. При этом в документации вполне чётко написано, что unsafe только в случаях, когда вы точно знаете что делаете.
Да, Rust накладывает дополнительные ограничения. Как раз с целью избежать определённого класса ошибок. Удобно вам такое разделение или нет — вопрос к вам. Безусловно, у каждого свои требования.
Да, про него значительно меньше информации. Работа в этом направлении ведётся. Как раз на этот год планируется увеличить количество документации, описать основные практики — т.е. сделать язык "ближе к народу". На наработку информации нужно время. Все эти книги по С++ тоже появились не мгновенно в день релиза. Они постепенно появлялись в течение последних 25 лет, вместе с расширением базы пользователей.
Про евангелизм не буду. Отвечу про "магическую" безопасность. Она не магическая. Это просто устранение довольно широкого класса ошибок работы с ресурсами путём добавления дополнительных статических гарантий. И то, что для отключения этих гарантий требуются дополнительные телодвижения.
По сути, проблема та же что в пункте 3. Нужно некоторое время. Та же сборка под bare metal постепенно улучшается.
Повторю, если я где-то ошибся в обобщении ваших тезисов — поправьте, я постараюсь ответить.
Начну с конца. Любым инструментом можно как сделать хорошо, так и сделать отвратительно.
По поводу вашего студента — а вы уверены, что на С он бы написал лучше? У меня пока складывается впечатление, что он пытался "вбить молотком" своё решение, не сильно задумываясь о результате. И в С ему бы это удалось гораздо легче.
По моему опять же сугубо личному опыту, Rust провоцирует писать более аккуратный код, более чётко выделять отдельные элементы, более тщательно продумывать, как будут обработаны те или иные нештатные ситуации. Именно из-за своих ограничений, которые не позволяют просто так создать, к примеру, иерархию объектов, которые в реальности ссылаются друг на друга "вверх", "вниз" и "вбок" совершенно произвольно.
По поводу переусложнения — мы всегда платим сложностью в одном месте, чтобы выиграть в другом.
В С мы выигрываем в простоте самого языка, но платим когнитивной нагрузкой — всю семантику кода приходится тащить в голове.
В С++ мы платим сложностью языка и сложностью некоторых абстракций за простоту "клиентского" кода — только что-то уж очень часто эти абстракции текут.
В .NET/Java мы платим сложностью рантайма за более простое написание кода. Который "ломается" менее явно, а потому вызывает иллюзию большей надёжности.
В Rust мы платим наличием ранее неизвестных концепций и более строгой системой типов за более строгое разделение безопасного и небезопасного кода.
В том же Haskell мы платим наличием рантайма и непривычной парадигмой за ленивость и отделение побочных эффектов от всего остального кода.
Не могу назвать себя спецом по Rust. Я скорее спец по С++, который интересуется Rust.
По поводу UI — не могу ответить. Скорее всего, использовать то что есть через биндинги. Проекты есть, но мне их статус неизвестен.
Что я точно не предлагаю — переписывать объёмные проекты. Куча муторной и часто ненужной работы. Вот писать новые модули ядра на Rust вроде как пытаются.
bindgen — пакет Rust для генерации биндингов к С библиотекам по заголовочным файлам.
tokio — асинхронный стек на основе фьючерсов для того же Rust.
Жалуюсь потому, что 2 года назад первый был в зачаточном состоянии, даже константы из макро-дефайнов транслировать не умел, а второго не было в принципе. В противном случае на один новый проект, написанный на Rust вместо С++, было бы больше.
Да, это потребует эмуляции АТД. Но без short-circuit return в выражениях оно будет выглядеть убого. Довольно забавно, что в С++, при всей необязательности исключений (а иногда и невозможности), до сих пор нет идиоматичного способа обработки ошибок без их участия.
На всякий случай уточняю, что описанная ерунда с раскруткой цепочки призвана применяться в месте, где ошибка показывается в окошке пользователю, а не повсеместно в коде.
Если коротко, есть некий проект где вся обработка ошибок строится на возврате true/false либо пустого/непустого объекта. Я пытаюсь подобрать схему обработки ошибок на базе цепочки исключений, построенной через http://en.cppreference.com/w/cpp/error/nested_exception. Проблема — код для прохода по этой цепочке выглядит откровенно криво:
FunctionRef здесь — аналог std::function, который только ссылается на callable.
Далее пользовательский код в месте отлова будет выглядеть как-то так:
Собственно, выходит минимум 2 throw на исключение. Пока ничего лучше я не придумал.
Альтернатива — делать свой NestedException.
По сути да
Это будет работать как выражение? Т.е. при наличии макры
можно будет
?
Можете поделиться ссылкой?
В очередной раз попытался реализовать обработку ошибок через исключения. Поохал от кол-ва try-catch при размотке nested_exception. В связи с noexcept и неоднозначным отношением к исключениям, возникла дурацкая мысль. Что если return станет выражением? Тогда (псевдокод конечно):
Дело именно в этом. Семантически такой "пустой объект" — всё ещё объект. К нему можно получить доступ, дёргать методы и т.п. Более того, С++ позволяет легко сделать ссылку-перемещение из простой мутабельной ссылки. В ряде случаев это может приводить к, например, неконтроллируемому появлению ссылок на элементы вектора, к примеру, которых уже давно нет на этом месте. Потому что исходный вектор был мало того что перемещён, а вдобавок реаллоцирован. В Rust такой перемещённый объект становится недоступен в принципе.
В Rust одна семантика.
Что конфликтует с лайфтаймами в С++ — принцип "копирование первично". Т.е. даже после перемещения объекта на его месте остаётся "заглушка" в неопределённом, пусть и валидном состоянии.Проконтролировать такое можно в простых случаях, с менее простыми будут проблемы.
Не выйдет. Семантика С++ конфликтует с такими проверками.
Окей. Тогда, чтобы я не утрировал, приведите пожалуйста критерии кода, который будете считать достаточным аргументом.
А теперь объясните, чем для вас плох этот пример.
На Rust? На Rust.
Системный код? Системный код.
Ансэйф изолирован? Изолирован, я смотрел.
Или вам подойдёт только Unreal Engine, написанный на Rust для квантового компьютера и имеющий пользовательскую базу не менее 10 миллионов человек?
Вполне возможно, вы найдёте ответ на свой вопрос по этой ссылке:
https://github.com/redox-os/redox
Да, это пока исследовательский проект. Но эта ОС вполне себе работает. Ядро написано на голом Rust.
Опять Хабр не туда постит комментарии… да что ж такое...
Отвечал не на тот комментарий.
Да. Согласен. Если код пишет идиот, который считает что компилятор/интерпретатор мешает его творческому полёту мысли, он может написать шлак на чём угодно, начиная с Asm и заканчивая Agda/Idris. Впрочем, до последних он скорее всего не дойдёт.
А где я вам противоречил?
Значит я вас таки неправильно понял. Отвечаю на поставленный вопрос
Если коротко — никак. Нет там магии и единорогов. Там есть возможность проверить на границе "безопасности" инварианты и получить дополнительные гарантии, помощь компилятора и проверки внутри безопасной зоны. Но ничто не помешает вам растянуть "границу безопасности" на весь код. Также как в ООП-языке ничто не помешает вам использовать паттерн "Паблик Морозов".
Я, кстати, не видел пока ни одного языка, следящего за дизайном приложения. Есть линтеры — но не встроенные в язык.
Просто чтобы не потерять нить рассуждения.
Вы утверждаете, что Rust ничем не лучше С++, т.к. точно также позволяет при определённом желании наговнокодить, потоптаться по памяти и сделать другие плохие вещи. Более того, он с важей т.з. хуже т.к. накладывает дополнительные ограничения. Также он хуже, поскольку про него меньше статей, best practices etc. Ещё вам, похоже, надоели евангелисты, кричащие на всех углах, как Rust безопасен. Могу понять. Но главное пожалуй то, что для него гораздо меньше наработанного кода, библиотек и поддержки платформ. Если я где-то ошибся, поправьте.
Теперь попробую ответить на ваше утверждение, приведённое в формулировке выше.
Повторю, если я где-то ошибся в обобщении ваших тезисов — поправьте, я постараюсь ответить.
Начну с конца. Любым инструментом можно как сделать хорошо, так и сделать отвратительно.
По поводу вашего студента — а вы уверены, что на С он бы написал лучше? У меня пока складывается впечатление, что он пытался "вбить молотком" своё решение, не сильно задумываясь о результате. И в С ему бы это удалось гораздо легче.
По моему опять же сугубо личному опыту, Rust провоцирует писать более аккуратный код, более чётко выделять отдельные элементы, более тщательно продумывать, как будут обработаны те или иные нештатные ситуации. Именно из-за своих ограничений, которые не позволяют просто так создать, к примеру, иерархию объектов, которые в реальности ссылаются друг на друга "вверх", "вниз" и "вбок" совершенно произвольно.
По поводу переусложнения — мы всегда платим сложностью в одном месте, чтобы выиграть в другом.
Не могу назвать себя спецом по Rust. Я скорее спец по С++, который интересуется Rust.
По поводу UI — не могу ответить. Скорее всего, использовать то что есть через биндинги. Проекты есть, но мне их статус неизвестен.
С++ — один сплошной unsafe блок. В Rust unsafe чётко локализован и легко поддаётся изоляции и верификации глазками.
Что я точно не предлагаю — переписывать объёмные проекты. Куча муторной и часто ненужной работы. Вот писать новые модули ядра на Rust вроде как пытаются.
bindgen — пакет Rust для генерации биндингов к С библиотекам по заголовочным файлам.
tokio — асинхронный стек на основе фьючерсов для того же Rust.
Жалуюсь потому, что 2 года назад первый был в зачаточном состоянии, даже константы из макро-дефайнов транслировать не умел, а второго не было в принципе. В противном случае на один новый проект, написанный на Rust вместо С++, было бы больше.