Comments 139

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


Конечно, можно возразить популярным "Настоящий С++ программист так никогда не напишет!", но отчеты Microsoft/Google/Mozilla, да и хотя бы того же PVS считают иначе.


А еще они имеют рантайм оверхед. Поэтому полагаю, иногда приходится специально выпиливать их и заменять на сырые указатели, чтобы ускорить на 5% выполнение горячего участка. А менять безопасность на скорость всегда грустно. Можно было бы перефразировать Франклина на эту тему, но, пожалуй, не буду.

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

да, я помню ваши «показательные» примеры:
auto p = new T;
unique_ptr<T> a(p);
unique_ptr<T> b(p);


А еще они имеют рантайм оверхед.

ага

Поэтому полагаю, иногда приходится специально выпиливать их и заменять на сырые указатели, чтобы ускорить на 5% выполнение горячего участка

но выпиливание умных указателей из того же раста идет через unsafe…

Конечно, можно возразить популярным «Настоящий С++ программист так никогда не напишет!», но отчеты Microsoft/Google/Mozilla

а можно взглянуть на эти отчеты?

да и хотя бы того же PVS считают иначе

по моей памяти подавляющее большинство ошибок, находимых PVS'ом, в коде, который старше того же раста. Возьмем например их последнюю проверку TON, «код написан на языке C++14 и насчитывает 210 тысяч строк». Ни одной ошибки памяти PVS не нашел.
да, я помню ваши «показательные» примеры

Ну да, а что не так? "Так никогда никто не напишет"? Я даже в расте такое встречал, правда там не компилировалось, и люди приходили в чат с вопросом "как бы мне сделать чтобы собралось". В плюсах не придут — компилится же.


ага

А зачем вам бокс опшне в расте? Надо тогда уж с Rc/Arc использовать, но тут фишка в том, что их вы будете юзать кое-где по необходимости, а большая часть кода будет верифицироваться бесплатными &T\&mut T, а вот в плюсах такого разделения нет. Придется платить всегда.


но выпиливание умных указателей из того же раста идет через unsafe…

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


а можно взглянуть на эти отчеты?

Да хотя бы этот https://www.cso.com.au/article/664150/microsoft-eyes-mozilla-rust-obliterate-c-memory-security-flaws/. Я надеюсь вы не будете предполагать, что майкрософт пишет в 2019 году С++ код без умных указателей, и сравнивал со старыми плюсами новый раст.

Ну да, а что не так? «Так никогда никто не напишет»?

а можете найти такой мисюз в каком-нибудь открытом проекте?

А зачем вам бокс опшне в расте? Надо тогда уж с Rc/Arc использовать

Rc/Arc — от «reference counted», который мне здесь не нужен. А нужен мне был прямой аналог std::unique_ptr, коим именно оптобокс и должен являться, т.к. Box — не nullable. Я же правильно помню, что там даже есть хитрая оптимизация, что отсутствие значения в оптобоксе сделано через null pointer а не отдельный флаг?

да, я потом могу грепнуть по ансейфу и найти потенциально проблемные места

так место падения в обоих случаях отлавливается по логам/дебаггером, а не чтением всей (и даже не только unsafe) кодобазы.

Да хотя бы этот www.cso.com.au/article/664150/microsoft-eyes-mozilla-rust-obliterate-c-memory-security-flaws.

но это не отчет. Это статья какого-то журналиста, спекулирующего над формулировкой вакансии, которую выложили MS. У них всё-таки не было официального заявления «мы переходим на раст!».
а можете найти такой мисюз в каком-нибудь открытом проекте?

Нет, я же не пишу на С++. У меня знакомые периодически в приватном чатике ругаются, но там искать долго, да и ссылку на чатик я не дам. Может humbug поделиться ссылочкой.


А нужен мне был прямой аналог std::unique_ptr

Пожалуйста — &mut


так место падения в обоих случаях отлавливается по логам/дебаггером, а не чтением всей (и даже не только unsafe) кодобазы.

Даже имея стектрейс и лог не всегда понятно где что упало. С ансейфом у вас останется скорее всего 1-2 места на 5-10 строчек где могло произойти что-то плохое. Неплохая экономия времени.


но это не отчет. Это статья какого-то журналиста, спекулирующего над формулировкой вакансии, которую выложили MS. У них всё-таки не было официального заявления «мы переходим на раст!».

Ну про переход они писали немного здесь: https://msrc-blog.microsoft.com/2019/09/30/building-the-azure-iot-edge-security-daemon-in-rust/


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

Пожалуйста — &mut

но это совсем не прямой аналог, ведь ссылка:
а. не определяет лайфтайм, а зависит от него
б. память под объект необязательно выделена в куче
Там, где в расте можно обойтись ссылкой, в плюсах это точно так же можно. Да, надо проверять соблюдения лайфтаймов. Но и false positive нет.

Ну про переход они писали немного здесь: msrc-blog.microsoft.com/2019/09/30/building-the-azure-iot-edge-security-daemon-in-rust

но это тоже не отчет. Это "мы написали один safe-critical daemon для одного сервиса на расте". Ваша цитата: "но отчеты Microsoft/Google/Mozilla, да и хотя бы того же PVS считают иначе.", я прошу предоставить ссылки на упомянутые отчеты, а не на спекуляции журналистов. Отчет — это документ с цифрами, аля «N уязвимостей памяти на M строк кода»

Даже имея стектрейс и лог не всегда понятно где что упало. С ансейфом у вас останется скорее всего 1-2 места на 5-10 строчек где могло произойти что-то плохое. Неплохая экономия времени.

«неплохая» это сколько? За последние пару лет я потратил на отладку ошибок памяти меньше времени, чем на вон те 10 строчек раста в годболте
а. не определяет лайфтайм, а зависит от него

Она определяет лайфтайм. Он просто не должен быть меньше, чем у объекта. Если объект передан во владение то он у вас уже есть в уникальном виде и вам даже &mut не нужен.


б. память под объект необязательно выделена в куче

А это даже хорошо.


я прошу предоставить ссылки на упомянутые отчеты, а не на спекуляции журналистов. Отчет — это документ с цифрами, аля «N уязвимостей памяти на M строк кода»

Окей, держите именно в такой формулировке: https://www.zdnet.com/article/microsoft-70-percent-of-all-security-bugs-are-memory-safety-issues/


«неплохая» это сколько? За последние пару лет я потратил на отладку ошибок памяти меньше времени, чем на вон те 10 строчек раста в годболте

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

Она определяет лайфтайм. Он просто не должен быть меньше, чем у объекта.

это называется «зависит от». Я не могу спокойно передать ссылку в другой поток, но могу передать смартпоинтер, т.к. ссылка не продлит время жизни объекта.

Если объект передан во владение то он у вас уже есть в уникальном виде и вам даже &mut не нужен.… А это даже хорошо.

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

Окей, держите именно в такой формулировке: www.zdnet.com/article/microsoft-70-percent-of-all-security-bugs-are-memory-safety-issues

«70% of all security bugs», а не «70% of all bugs».

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

Гением себя не считаю, а лавры отдал бы старому доброму код ревью.
П.с. лесть вам не поможет
это называется «зависит от». Я не могу спокойно передать ссылку в другой поток, но могу передать смартпоинтер, т.к. ссылка не продлит время жизни объекта.

Берете канал и передаете безо всяких смартпоинтеров.


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

Ну тогда придется расчехлять аналог unique_ptr, да. Только локально и в конкретных местах. По моему опыту приходится этим заниматься не чаще чем в нескольких процентах от всех ссылок, что используются в программе.


«70% of all security bugs», а не «70% of all bugs».

Ну, у макйрософта другой статистики не приведено. Мозилла рассказывала про опыт переписвания файрфокса, там емнип были цифры были что порядка 50% багов которые они зафиксили в файрфоксе на расте бы не скомпилировались. Я в комментах на хабре пару месяцев назад кидал ссылку, сейчас искать лень.

Берете канал и передаете безо всяких смартпоинтеров.

так я же уже несколько причин привел почему я могу хотеть именно смартпоинтер. Вы объекты килобайт в 16 тоже будете гонять между потоками копиями?

Мозилла рассказывала про опыт переписвания файрфокса, там емнип были цифры были что порядка 50% багов которые они зафиксили в файрфоксе на расте бы не скомпилировались.

мозилла начали разрабатывать раст в 2007 году из-за длинной истории уязвимостей. В 2007 даже с++11 не было. И даже сейчас вероятно у них большая часть кода — застарелый си с классами. Его и на новые плюсы переписать можно и быстрее, и надежнее

По моему опыту приходится этим заниматься не чаще чем в нескольких процентах от всех ссылок, что используются в программе.

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

Да пожалуйста, если хотите. Программист на фортране может писать на фортране на любом языке программирования. Только странно не использовать плюсы языка, которые он дает.


Вы объекты килобайт в 16 тоже будете гонять между потоками копиями?

Объекта в килобайт у вас скорее всего и не будет. Большие объекты как правило состоят из строк и векторов, у которых на стеке только адрес и длина.


мозилла начали разрабатывать раст в 2007 году из-за длинной истории уязвимостей. В 2007 даже с++11 не было. И даже сейчас вероятно у них большая часть кода — застарелый си с классами. Его и на новые плюсы переписать можно и быстрее, и надежнее

А с 2007 года они на С++ ничего не писали получается?


да это то понятно, но мне неинтересно сравнивать ссылки с ссылками ибо примерно то же самое

Не писать лишнего, но и не терять в безопасности, вот что интересно.

Программист на фортране может писать на фортране на любом языке программирования. Только странно не использовать плюсы языка, которые он дает.
Объекта в килобайт у вас скорее всего и не будет.

Ну вот бывают (де-факто) объекты размером в несколько килобайт, которые не «состоят из строк и векторов». Я вам уже кучу аргументов привел почему я могу хотеть именно гребаный unique_ptr а не «Arc/Rc», не мув, не ссылку и не канал. Не надо мне приводить заведомо неподходящие альтернативы, не надо мне рассказывать как часто это нужно и не надо меня стебать всякими «когда в руках молоток всё кажется гвоздями» — я не первый день работаю и я знаю что может быть необходимо в конкретной ситуации. Почему так сложно дискутировать по существу? Неужели дизлайк — самое конструктивное что от вас можно добиться? Где уважение к собеседнику, с которым вы спорите?

Вы же архитектуру программы строите основываясь не на ошибках компиляции? «Ну пусть это будет ссылка, пока компилятор не ругнется, а потом если что пройдемся автозаменой на Rc, и всё будет зашибись до потребности в Arc.»?
Я вам уже кучу аргументов привел почему я могу хотеть именно гребаный unique_ptr а не «Arc/Rc», не мув, не ссылку и не канал.

А чем Вас Box не устраивает? При том, что unique_ptr может и обмануть с уникальностью после release, а Box не может?

И? Разыменовать указатель без unsafe вы все равно не сможете, а в unsafe вы сами должны продемонстрировать что не нарушаете инварианты.

а в коде на плюсах я гарантирую инварианты сам, да, не надо пожалуйста повторять про safe/unsafe миллионный раз, это утомляет. Box позволяет те же самые потенциально небезопасные операции что и unique_ptr, пусть и под unsafe. Кроме двух: nullable и кастомного деструктора. Второго нет и у оптобокса. Так во что на расте оборачивать половину сишных либ?

Мне нужен был прямой аналог std::unique_ptr для демонстрации оверхеда умных указателей. Вы предложили несколько вариантов как обойтись без умных указателей, вообще не адресовав сабж.
а в коде на плюсах я гарантирую инварианты сам, да, не надо пожалуйста повторять про safe/unsafe миллионный раз, это утомляет. Box позволяет те же самые потенциально небезопасные операции

Фишка в том, что проверять инварианты на паре сотне строк проекта в десятки тысяч строк намного проще, чем каждую из этих тыщ строк.


Так во что на расте оборачивать половину сишных либ?

Обмазываться ансейфом и оборачивать. Что поделать, раст не настолько крут чтобы верифицировать код на других языках.


Мне нужен был прямой аналог std::unique_ptr для демонстрации оверхеда умных указателей. Вы предложили несколько вариантов как обойтись без умных указателей, вообще не адресовав сабж.

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




И кстати, если вы вдруг не знали. в расте я могу запретить на уровне приложения ансейф. Нельзя будет вот так взять и два раза из одного указателя сделать два бокса. Ошибка компиляции будет на ансейф. Как в плюсах такое запретить не представляю.

сразу после того, как я написал:
да, не надо пожалуйста повторять про safe/unsafe миллионный раз, это утомляет.

вы ответили тремя абзацами про safe/unsafe и одним по существу. Умоляю, хватит.

Ну окей, берите бокс, только зачем вам исскуственный оверхед-то?

я привел минимальный пример «проверить указатель и разыменовать его», и сделал эквивалентную реализацию на двух языках. Почему вы требуете чтобы она была не эквивалентной?

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

это не так, юзкейсы полностью эквивалентны. Если соберетесь опровергать это утверждение — умоляю, не надо в миллионный раз про safe/unsafe. Можете просто дать ссылку на функционально эквивалентный код, корреткный в расте и с UB в плюсах.

И кстати, если вы вдруг не знали. в расте я могу запретить на уровне приложения ансейф

рекурсивно?
я привел минимальный пример «проверить указатель и разыменовать его», и сделал эквивалентную реализацию на двух языках. Почему вы требуете чтобы она была не эквивалентной?

В расте нет эквивалентной операции. Там есть 3 разных (move, &mut, Box) которые используются каждые в своей области. Вы выбрали только третью, просто потому что вам так захотелось.


Это примерно как доказывать, что плюсцы не быстрее джавы, потому что если написать свой наколеночный сборщик мусора, то он будет даже медленнее стандартного. Можно еще привести с годболта листинг сборщика джавы для приложения, своего наколенчного на плюсах и увидеть, как они 1:1 похожи.


это не так, юзкейсы полностью эквивалентны. Если соберетесь опровергать это утверждение — умоляю, не надо в миллионный раз про safe/unsafe. Можете просто дать ссылку на функционально эквивалентный код, корреткный в расте и с UB в плюсах.

Да че тут опровергать:


fn increment(x: &mut i32, y: &mut i32) { 
   *x += 1; 
   *y += 1;
}

Никаких боксов нет. Покажите, как на плюсах безопасно без смартпоинтеров это написать.


рекурсивно?

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

Вы выбрали только третью, просто потому что вам так захотелось.

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

Никаких боксов нет. Покажите, как на плюсах безопасно без смартпоинтеров это написать.

так проблема не в том, что я не понимаю растовый unsafe, а в том, что вы не понимаете плюсовый UB. Вот этот код:
void foo(int& x) { x = 10; }

является полностью корректным и безопасным, ровно до тех пор, пока в него не будет передана мертвая ссылка. И прежде чем вы попытаетесь привести аргумент «но в него же может быть передана мертвая ссылка!» прошу ознакомиться с таким примером вызова вашей foo:
foo(unsafe { &mut *(std::ptr::null_mut() as *mut i32) });

И меня не интересует чей код прав а чей виноват. Я попросил привести
функционально эквивалентный код, корреткный в расте и с UB в плюсах.


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

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

Нет, конечно, потому что у них разные гарантии.


прошу ознакомиться с таким примером вызова вашей foo:

Но это не является кодом на Rust. Напомню, что код на Rust не может содержать UB, поэтому это код на каком-то растоподобном псевдоязыке. В расте такой проблемы нет.


функционально эквивалентный код, корреткный в расте и с UB в плюсах.

Да пожалуйста


if x > x + 1 {
   println!("Overflow!");
}

Ну если доверять кому угодно кроме себя, то да, нужны презервативы

Я вот нахожу забавным скорее желание человека обвинить разработчиков компилятора, стд, библиотек, но считать свой код свободным от багов (ну или хотя бы того же качества). Очень самонадеянно, должен сказать.

Да пожалуйста

Вот кстати с signed overflow в расте сделали довольно забавно с моей точки зрения. В режиме отладки проверка действительно реализована, а в режиме релиза просто заявлено, что будет two’s complement wrap. Интересно, какая ламбада будет, когда rust понадобится портировать на архитектуру с представлением отрицательных чисел в one's complement (а такие вполне себе еще есть).
Интересно, какая ламбада будет, когда rust понадобится портировать на архитектуру с представлением отрицательных чисел в one's complement (а такие вполне себе еще есть).

Такая же, как в C++20, из которого отличные от two's complement числа выпилили.

Ну что ж, будем надеяться, что новых архитектур с one's complement больше не будет.
Нет, конечно, потому что у них разные гарантии.

в чем отличия их гарантий внутри unsafe?

Но это не является кодом на Rustс++. Напомню, что код на Rustс++ не может содержать UB, поэтому это код на каком-то растос++-подобном псевдоязыке. В растес++ такой проблемы нет.


Да пожалуйста

этот код полностью эквивалентен.

п.с. а что, раст даже в перегрузку не умеет?

Я вот нахожу забавным скорее желание человека обвинить разработчиков компилятора, стд, библиотек, но считать свой код свободным от багов (ну или хотя бы того же качества). Очень самонадеянно, должен сказать.

да никто никого не обвиняет. Просто я знаю, что разработчики библиотек, которые я использую, такие же люди как и я, и тоже могут ошибаться. Поэтому позицию «им я доверяю, а себе — нет» я не разделяю.
в чем отличия их гарантий внутри unsafe?

В том, что unsafe не отключает гарантий раста.


п.с. а что, раст даже в перегрузку не умеет?

Не даже, а вполне сознательно не умеет. Зато можно выводить тип по результату функции. В целом, удобный трейдоф.


этот код полностью эквивалентен.

Да какой эквивалентен, когда вы специально написали совсем иначе чтобы не стриггерить UB.


да никто никого не обвиняет. Просто я знаю, что разработчики библиотек, которые я использую, такие же люди как и я, и тоже могут ошибаться. Поэтому позицию «им я доверяю, а себе — нет» я не разделяю.

Зависит от библиотек. В моем случае я редко использую библиотеку меньше чем десятков миллионов скачиваний, и качество там соответствующее.

В том, что unsafe не отключает гарантий раста.

то есть до тех пор пока я всё делаю верно, раст только вставляет мне палки в колеса своим примитивным borrow checker'ом?

Да какой эквивалентен, когда вы специально написали совсем иначе чтобы не стриггерить UB.

написал-то иначе, а поведение 100%-но эквивалентно. Я даже могу написать на эту функцию compile-time тесты, не пройдя которые программа не скомпилится. А rust так умеет?

Зависит от библиотек. В моем случае я редко использую библиотеку меньше чем десятков миллионов скачиваний, и качество там соответствующее.

а что, скачивания увеличивают качество библиотеки? Я ловил баги в Qt'е из-за редкого юзкейса, а у rust-враппера для fftw3 (сишная либа, с которой я больше всего работал) всего 2700 скачиваний, то есть по вашей логике надежнее написать самому?

Всё у раста круто, пока в песочнице играешься. А за её пределами — одни надежды
Но это не является кодом на Rust. Напомню, что код на Rust не может содержать UB, поэтому это код на каком-то растоподобном псевдоязыке. В расте такой проблемы нет.

Отлично, отлично. Ламбада на 5+. По такой же логике, код на C++ не может содержать UB (иначе он будет incorrect), поэтому любой такой код — это код на каком-то C++-подобном псевдоязыке, а в C++ такой проблемы нет и быть не может. Защита Чубаки какая-то :)

P.S. Добавил ссылку на ваш чудесный комментарий в закладки.
Отлично, отлично. Ламбада на 5+. По такой же логике, код на C++ не может содержать UB

Совершенно верно.


Осталось только понять, можно ли за конечное число шагов установить, является ли код валидным на этом языке или нет. Учитывая, что даже парсинг в C++ это в общем случае неразрешимая задача, то вопрос весьма актуален для них.

Для unsafe rust (без которого safe rust по большому счету бесполезен) задача в этом плане ничем не отличается. Иначе утилиты типа нижеупомянутой miri были бы не нужны.

Отличается, конечно. unsafe-блок не должен нарушать гарантий раста. Если он не нарушает, то все ок. Вам не нужно знать что происходит в других функциях, достаточно посмотреть на 4 строчки в блоке.

Нет, к сожалению, нужно. Я ниже приводил пример бага с hash_raw_entry из std. То есть мне очень даже надо знать не только что происходит в других моих функциях, но и что происходит в std, и что происходит в чужих библиотеках тоже. Там тоже может быть все что угодно.

Ну тогда это не про вас.


У меня лично нет времени проводить аудит всех зависимостей и стд.

Значит, ни вы, ни я не способны «за конечное количество шагов» убедиться, что данная программа не является на самом деле «программой на каком-то растоподобном псевдоязыке» :)
Значит, ни вы, ни я не способны «за конечное количество шагов» убедиться, что данная программа не является на самом деле «программой на каком-то растоподобном псевдоязыке» :)

Ваша программа — является. Зависимости — ну наверное может оказаться, что нет.


Мы же все это делаем для получения практических результатов, а не носиться с сейф\ансейф на руках.




Предлагаю переформулировать чтобы не возникало разночтений. С растом у вас в вашем коде не будет проблем с памятью. Если вы всегда пишете идеальный код, а проблемы с памятью всегда в библиотеках — то что поделать, компиляторы УБ не умеют ловить, на то они и уб, тогда раст не дает ничего по сравнению с плюсами.


С другой стороны если у вас зависимости тоже ансейф не используют, то и там проблем не будет. Так работает рекурсивная порука безопасности.

Ваша программа — является. Зависимости — ну наверное может оказаться, что нет.

Я уже отвечал тут humbug'у про это. Когда клиент ко мне обращается с претензией, что моя программа падает, я не могу сделать лицо кирпичом и сказать «правила локальности раста формально гарантируют, что моя программа локально корректна, так что проследуйте, пожалуйста, в жопу». Я должен буду это падение как-то исправлять, не ограничиваясь лишь собственным кодом. Поэтому «гарантии» растопесочницы для меня как бы не гарантии вообще.

Мы же все это делаем для получения практических результатов, а не носиться с сейф\ансейф на руках.

Ага, и я о том же.
Я уже отвечал тут humbug'у про это. Когда клиент ко мне обращается с претензией, что моя программа падает, я не могу сделать лицо кирпичом и сказать «правила локальности раста формально гарантируют, что моя программа локально корректна, так что проследуйте, пожалуйста, в жопу».

Смотрите утрированный пример. Вы пишете программу, у которой зависимости от serde_json, hyper и ваша вспомогательная либа. Так вот, баг в serde вы вряд ли поймаете, в hyper тоже, в вашем коде тоже проблем с памятью нет. Итого — код сразу работает ожидаемым образом.


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

Вашими бы устами да мед пить.

P.S.

Предлагаю переформулировать чтобы не возникало разночтений. С растом у вас в вашем коде не будет проблем с памятью.

Не будет, пока я пишу тривиальщину, для которой достаточно убогой функциональности borrow checker'а. Как только нужно реализовать что-то нетривиальное и вступает в дело написание unsafe кода — так это утверждение ломается.

И что? Ошибки есть везде, проблема в том, что вы скорее всего с ними не столкнетесь. Я вот за 6 лет ни разу не словил ошибки csharp compiler, а они там все это время что-то регулярно чинят. Да и из знакомых не слышал ни кого, кто хотя бы issue с проблемой с которой лично столкнулся открыл. Магия?

Я и в собственном-то коде не припомню когда ловил SIGSEGV в последний раз. Но вы сейчас скажете, что «это ничего не доказывает». Верно, не доказывает. Как и то, что вы еще не столкнулись с багами в rustc, std или собственном unsafe коде, никоим образом не доказывает, что их там нет.
Я и в собственном-то коде не припомню когда ловил SIGSEGV в последний раз. Но вы сейчас скажете, что «это ничего не доказывает».

Ну значит вы молодец. А я вот NullRefernce всякие регулярно ловлю, и прочие вещи, которые растовая ситсема типов отлавливает. Сегфолт тоже ловил когда с opencv работал. Видимо, поэтому мне он нравится.

Ещё можно на границе баги ловить. Не те ожидания от API, не выраженные в типах, и всё такое.


Я так регулярно ловлю креши в своём хаскель-проекте, на FFI в clang.

Вообще нет. Я уже перечислил вам правила локальности, но вы их упорно игнорируете. Это в С++ необходимо доказывать безопасность приложения в целом, а в Расте достаточно локального анализа. https://doc.rust-lang.org/nomicon/working-with-unsafe.html

Видите ли, когда мне нужно будет в отладчике ковыряться, почему у меня программа падает, я не смогу просто взять и сказать «это не у меня, у меня весь код safe». Мне нужно будет найти, почему она падает и где именно, и как максимум исправить это (и послать патч в upstream) а как минимум — хотя бы сделать workaround. Ваши бумажные правила локальности для меня абсолютно бесполезны.

Так это не бумажные правила, у это следствия формального доказательства корректности системы типов Rust. Если вы не верите в математику, у меня для вас плохие новости.

По вашей же ссылке:

Be aware that Miri will not catch all cases of undefined behavior in your program, and cannot run all programs.

Так что давайте без громких слов.
Видите ли, когда мне нужно будет в отладчике ковыряться, почему у меня программа падает, я не смогу просто взять и сказать «это не у меня, у меня весь код safe».

Успехов с отладчиком, если программа весит 100-150 гигабайт в памяти, раздел для корок на прод-машине меньше, и бага воспроизводится только через несколько дней работы в ночь с каждой второй пятницы на субботу, когда половина кластера уходит в плановый ребут.


В моём опыте в тырпрайзе такие вещи обычно чинятся тем, что программа добавляется в ежедневный список на рестарт.

Ну хотя бы даже и так. В любом случае что-то делать мне будет нужно, морду кирпичом "проблема не у меня" сделать не выйдет.

открываем первый пример


if idx < arr.len() {
        unsafe {
            Some(*arr.get_unchecked(idx))
        }
    }

unsafe ничего не знает про idx, он может прийти откуда угодно, значит надо проверять всю программу.

Нет, не надо, нам же не важно, откуда оно пришло, нам важно, какое значение оно принимает.


Если idx < arr.len() то документация разрешает нам использовать unsafe-метод.
Если нет, то не разрешает, но тогда мы в if не попадем.


В итоге обе ветки выполнения флоу не нарушаются, вне зависимости "откуда" там что пришло.

Eсли бы я хотел иметь проверку idx < arr.len() прямо перед обращением к индексу я бы сразу использовал safe метод!


Нет, не надо, нам же не важно, откуда оно пришло, нам важно, какое значение оно принимает.

У вас if снаружи unsafe внутри unsafe важно.

Eсли бы я хотел иметь проверку idx < arr.len() прямо перед обращением к индексу я бы сразу использовал safe метод!

ну так а если вы вынесите проверку if из метода, то вы не можете вызывать этот ансейф-метод без UB, все верно.


У вас if снаружи unsafe внутри unsafe важно.

Ну так unsafe принято оборачивать только то, где он нужен. Для вызова метода len() ансейф не нужен.

Очевидно что можно писать вокруг каждого unsafe if-ы и проверять инварианты.


Но во-первых даже если так делать (но вдруг забыть) фактически (т.е. место в коде в котором нужен патч) ошибка будет в if-ах а не в unsafe. Т.е. уже история с грепом по unsafe это некое преукрашательство.


А во-вторых unsafe затем и нужен что я могу отключить проверки для ускорения программы при реализации хитрого алгоритма, когда гарантии следуют из вышестоящей логики алгоритма, а не тупого ифа строчкой выше. Например я делаю тип ValidIndex который я могу отдавать наружу пользователю либы но через инкапсуляцию я как писатель либы знаю что в нём будет всегда хороший индекс, тода я могу в своей либе опустить проверки при использовании ValidIndex. Это пример надуманный.


Но например в std большое количество unsafe, и доказать что там работает всё коректно т.е. внешний апи safe было не быстрой задачей. Тут кидали ссылку где-то выше (может и вы сами) на статью.


Теперь мы приходим к моему тезису изначальному "значит надо проверять всю программу" — естественно не прямо всю целиком, а какой то модуль или класс, в простых случаях действительно только функцию или только 5ть строк, где происходит заворачивание ансейф в сейф.

Но во-первых даже если так делать (но вдруг забыть) фактически (т.е. место в коде в котором нужен патч) ошибка будет в if-ах а не в unsafe. Т.е. уже история с грепом по unsafe это некое преукрашательство.

Смотрите, вы условно говоря должны сделать тип


pub struct ValidIndex { value: i32, len: i32 };

impl ValidIndex {
    pub fn new(x: i32, len: i32) -> Option<Self> {  
        if x >= len { 
           None
        }
        else { 
           Some(new_unsafe(x, len));
        }
    }

    pub unsafe new_unsafe(x: i32, len: i32) -> Self {
       Self {value: x, len}
    }
}

И дальше если вы где-то накосячили с индексом то будет виновато то место, которое криво вызвало new_unsafe. Опять же, грепаем по этому месту и ищем виновных.


В итоге по построению у вас не будет с этим типом проблем, даже если вы его пользователю отдали.


Теперь мы приходим к моему тезису изначальному

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

вы упрощаете в своем примере, например тут описывается реализация arena https://exyr.org/2018/rust-arenas-vs-dropck/, и уже инварианты не такие тривиальные.


Я тоже написал на расте жсон/хмл/sql молотилку и тоже без ансейф и всё хорошо :)

без которого safe rust по большому счету бесполезен

Вот это кстати забавное замечание. На гитхабе полно проектов, где нет ни одного unsafe, и следовательно там нет этих проблем. Говорить что "ну там где-то под ковром произойдет в стд вызов FFI и все превратиться в тыкву" это полная чушь.

Это не чушь, а суровая реальность. И даже не ffi, а криво написанного метода из std.

Ну тогда остается только взять и повеситься, потому что стд любого языка это базиси которому все доверяют. И в котором не сомневается, пока не начнется странная дичь.




Хочется вспомнить одну цитату


Programming Defeatism: No technique will remove all bugs, so let's go with what worked in the 70s.

У вас есть язык, который дает некоторые гарантии. ОКей, он не приставит свою голову, и работает не в 100% случаев, а в 99,9999%. Ну и ладно, в оставшихся десятитысячных процентах можно и руками покопаться в дбг, чай не каждый раз придется этим заниматься.


Мы вот написали прототип на 20к строк и не получили ни одной ошибки в рантайме. В плюсах думаете, получилось бы (при том, что плюсы мы не знаем, и раст тоже изучали по мере написания)?

Криво написанные методы были и в c/c++ stdlib
И что это тогда меняет ?

Но это не является кодом на Rust. Напомню, что код на Rust не может содержать UB, поэтому это код на каком-то растоподобном псевдоязыке. В расте такой проблемы нет.

Спасибо повеселили. Код с UB не является кодом на С++. Корректный код на С++ не может содержать UB. Я ведь тоже так могу перефразировать.


Кстати гарантии для вызова unsafe они не в unsafe блоке содержатся. Если вы хотите разименовать указатель внутри unsafe блока Вы должны гарантировать что он валидный, а создан он мог быть вообще в абсолютно другом месте программы. Поэтому не всё так радостно, но от части ошибок защищает и при правильном подходе позволяет делать cвои safe api на основе unsafe инструкций.

Кажется, такого очень мало. Мне сходу в голову только одно приходит:

а вот тут вам потребуется ссылка к (не существующему) стандарту раста, в котором прямым текстом сказано, что такая конверсия well-defined.

Однако с уверенностью можно сказать что конверсия через честное копирование байт будет корректна в обоих языках. Если, разумеется, не пытаться использовать float, полученный таким образом
  1. Это используется в стандартной библиотеке.
    Например тут и во многих других аналогичных местах делается что-то вроде transmute с помощью union.
  2. https://doc.rust-lang.org/nightly/reference/items/unions.html#reading-and-writing-union-fields: Unions have no notion of an "active field". Instead, every union access just interprets the storage at the type of the field used for the access. Reading a union field reads the bits of the union at the field's type. (Конечно, если чтение породит невалидное значение, то поведение не определено).
doc.rust-lang.org

но это не стандарт языка rust, а reference manual к одному его компилятору. По аналогии, на любом компиляторе c++ который вы сможете найти, ваш пример будет работать аналогично.

Сейчас вы уводите разговор в совершенно иную плоскость: мы все-таки про UB говорим а не про стандартизацию.
И да, reference — это то, что (в идеале) должно дорасти до стандарта.


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

И где в документации clang, g++ и msvc написано, что они гарантируют, что чтение неактивного поля это не всегда UB?

Сейчас вы уводите разговор в совершенно иную плоскость: мы все-таки про UB говорим а не про стандартизацию.

так мы говорим про формальное или фактическое наличие/отсутствие UB? Если формальное, то у раста нет стандарта (мало ли, вдруг этот пункт они заимствуют?), а фактически компилятор раста ведет себя так же, как и clang.

И где в документации clang, g++ и msvc написано, что они гарантируют, что чтение неактивного поля это не всегда UB?

В доке gcc указано, что он предоставляет сишные гарантии в виде расширения. С доками clang/msvc сложнее. Сlang'овская страничка до безобразия аскетична, известно лишь, что он максимально эмулирует gcc. У msvc в принципе тяжело что-то искать, но сами они этот type punning via union используют.
У msvc в принципе тяжело что-то искать, но сами они этот type punning via union используют.

В C type punning совершенно законен. Хотя он и может породить trapped representation.
В C type punning совершенно законен.

а хедер windows.h используется не только в сишном коде

Уверены? Он и явно встречается, например, при передаче владения в C-функции. А уж неявно при присвоении одного unique_ptr другому…

1. вы и в расте передаете владение в сишные функции через Box::into_raw().

2. никакого «неявно при присвоении одного unique_ptr другому» нет попросту потому, что unique_ptr нельзя присваивать другому unique_ptr, только мувить.
Ну вот не лень Вам спорить ради спора? Вы не понимаете, что имеется в виду?
Ну вот не лень Вам спорить ради спора? Вы не понимаете, что имеется в виду?

я-то понимаю что имеется в виду, вам бы читать и внимать а не спорить.
В Rust принято все аспекты явно выделять — заверните box в option — и вуаля. В C и C++ традиционно любят «сворачивать» в один тип для краткости записи. Можно бессмысленно спорить, какой подход лучше, дело вкуса.
Ну, а «выдрав» указатель из Box'а Вы явно делаете что-то, нарушающие правила языка. Иногда приходится, да. Но такое не назовешь случайной ошибкой по невнимательности.
Ну, а «выдрав» указатель из Box'а Вы явно делаете что-то, нарушающие правила языка. Иногда приходится, да. Но такое не назовешь случайной ошибкой по невнимательности.

собственно, unique_ptr::release() тоже нечасто встречается, и тоже провоцирует внимательное отношение. Как и unique_ptr(new T(...)) вместо make_unique(...), и прочие подобные конструкции.
собственно, unique_ptr::release() тоже нечасто встречается

Уверены? Он и явно встречается, например, при передаче владения в C-функции. А уж неявно при присвоении одного unique_ptr другому…
Ну вот бывают (де-факто) объекты размером в несколько килобайт, которые не «состоят из строк и векторов».

Ну окей, берите для своего единственного (вероятно) во всей программе объекта в несколько килобайт бокс. Только это не значит что бокс это аналог unique_ptr.


Неужели дизлайк — самое конструктивное что от вас можно добиться? Где уважение к собеседнику, с которым вы спорите?

Я вам ни одного дизлайка ни разу не поставил, я им вообще редко пользуюсь.


Вы же архитектуру программы строите основываясь не на ошибках компиляции? «Ну пусть это будет ссылка, пока компилятор не ругнется, а потом если что пройдемся автозаменой на Rc, и всё будет зашибись до потребности в Arc.»?

Нет, я пользуюсь принципом относительной достаточности. Если у меня есть локальная переменнация цикла, я не буду её инкрементить на случай "а вдруг к ней будет доступ из многих потоков". Когда будет — добавлю интерлокед. А пока это как трата лишних ресурсов, так и неоправданное усложнение кода. "Как программисты хлеб пекли" — хороший эталон как надо разрабатывать программу.

Только это не значит что бокс это аналог unique_ptr.

ну назовите полнофункциональный аналог std::unique_ptr в расте, и в следующий раз в сравнении эквивалентного кода я использую его.

«Как программисты хлеб пекли» — хороший эталон как надо разрабатывать программу.

Да, код не обязан быть расширяемым и не должен быть переусложненным. Однако он обязан быть переиспользуемым, иначе малейшие изменения влекут рефакторинги, и я слишком много раз стукался об эту проблему. Тем более когда пишешь не пета, а что-то командное.
а можете найти такой мисюз в каком-нибудь открытом проекте?

Прямо такой — нет. А вот когда в unique_ptr/shared_ptr заворачивался объект, который на самом деле был на стеке или управлялся другим образом (внутри сишечки или через кутешные деревья владения), например, я встречал.


Можно, конечно, сказать, что на современных плюсах так не пишут, но если вы можете выкинуть весь легаси-код, то вы на самом деле выкидываете одно из основных (если не основное) преимущество плюсов.

Прямо такой — нет.

Я оспаривал конкретный пример.

Можно, конечно, сказать, что на современных плюсах так не пишут, но если вы можете выкинуть весь легаси-код, то вы на самом деле выкидываете одно из основных (если не основное) преимущество плюсов.

Как вы думаете, кто допустит меньше ошибок при оборачивании сишной либы — плюсовик со стажем, знакомый с си хотя бы через плюсы, или растовик, который убежал от си/плюсов потому что «небезопасно»?
Как вы думаете, кто допустит меньше ошибок при оборачивании сишной либы — плюсовик со стажем, знакомый с си хотя бы через плюсы, или растовик, который убежал от си/плюсов потому что «небезопасно»?

Зависит. У плюсовика стаж может быть в написании опердней, а растоман может не интернализировать концепцию владения и просто методом случайного тыка добиваться отсутствия матюгов компилятора (я таким образом сейчас coq ковыряю).

Зависит. У плюсовика стаж может быть в написании опердней, а растоман может не интернализировать концепцию владения и просто методом случайного тыка добиваться отсутствия матюгов компилятора (я таким образом сейчас coq ковыряю).

и в багах, посаженных при таком подходе, разумеется будет виноват си.
Вот я плюсовик со стажем, который убежал от си/плюсов, потому что небезопасно. И поведение кода зависит от положения Луны в знаке Скорпиона.

Из недавних примеров: есть libsodium, написанная на С. Внутри кастомная сборочная система на autotools, которая генерирует хтонический писец. Автору многократно предлагали кроссплатформенный CMake, но мы же не идем легкими путями? В итоге поведение функций `crypto_sign_ed25519` и `crypto_sign_ed25519_detached` на распространенных таргетах(win,lin,mac) правильное (предположительно), а на wasm32-wasi ведет себя [непредсказуемо](https://github.com/jedisct1/libsodium/issues/879). И все бы ничего, но если ручками компилировать через `CC… *.c`, то поведение этих функций на wasm32-wasi становится аналогичным поведению на таргетах большой тройки.

Вопрос в студию: вот вы у нас такой опытный плюсоид, отдебажьте, почините. Ведь С — это просто! Это надежно! Безопасно!

А вот у github.com/rustcrypto таких проблем нет. Потому что пишут на безопасном языке.
Открываем первую же либу по ссылке и видим 4 небезопасных алгоритма. Окай… смотрим что там за алгоритмы:
  1. md2, ~100 строк, 1 unsafe
  2. md4, ~150 строк кода, 1 unsafe
  3. md5, ~250 строк кода, 1 unsafe
  4. sha1, ~500 строк кода, 1 unsafe

Итого: 4 критических уязвимости на 1000 строк «формально безопасного» кода, большая часть которых — бойлерплейт. Зато не падает, это же критерий корректности, верно?

Почему вы взяли что там баг? Просто какое то кастование буферов, скорее всего будет ненужно когда завезут const generics наконец и будет нормальные GenericArray в std.

Почему вы взяли что там баг?

Потому что сами авторы библиотеки пометили эти алгоритмы как «Attack demonstrated in practice: avoid if at all possible».

* поправка: в md2/md4 нет unsafe. Те, что есть в md5/sha1 действительно просто «кастование буферов»
Потому что сами авторы библиотеки пометили эти алгоритмы как «Attack demonstrated in practice: avoid if at all possible».

Потому что проблемы в алгоритмах, а не в их реализации на расте. Открываем и читаем



Вы там галоперидольчику не забыли принять?

Раст не настолько крут чтобы проверять алгоритмы хеширования на наличие легко-генерируемых коллизий :)

Ну это как бы ладно, там просто сами алгоритмы не очень. А так растовские крейты богаты в целом на CVE:

cve.mitre.org/cgi-bin/cvekey.cgi?keyword=rust

Я не считал, сколько там их за этот год, но несколько десятков точно. И проблемы все знакомые — buffer overflow, use-afer-free, memory corruption, double free, out-of-bounds access, вот это вот все. Все как обычно.
Насчет «грепнуть по ансейфу» — вот статья о реальном процессе отладки сегфолта в приложении на rust:

jvns.ca/blog/2017/12/23/segfault-debugging

Все как положено — анализ стека (из которого понятно, что падение происходит вовсе не там, где на самом деле скрывается проблема, как это обычно и бывает), valgrind, постепенное выкидывание всего лишнего до MWE (ну в данном случае скорее MNWE, хехе), и, наконец, проблема найдена. Процесс, я бы сказал, стандартный, и весьма сильно отличающийся от благостной картины «грепану по ансейфу».

По-моему, "грепнуть по ансейфу" — это, все же, чуть меньший миф, чем плюсовое "грепнуть по const_cast/reinterpret_cast".

Ни разу не видел плюсовика, предлагающего для исправления бага "просто грепнуть по reinterpret_cast". А вот утверждения про "грепнуть по unsafe" вижу регулярно, да хотя бы в данном обсуждении.

А разве само наличие разных способов каста не оправдывается в том числе возможностью грепнуть по ним?

Оно оправдывается в первую очередь тем, что это РАЗНЫЕ касты, и работают по-разному. Лично я никогда не встречался с точкой зрения "они разные затем, чтобы по ним было удобно грепать". Это все равно, что сказать "const или virtual придуманы для того чтобы по ним грепать".

Возможно, у меня ложная память, но мне опять-таки казалось, что они разные и работают по-разному как раз чтобы отделить "опасные" от "безопасных". И, типа, "если вы используете const_cast, то вы, скорее всего, не правы" — Core Guidelines примерно так гласят, вроде.


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


Я не спорю, что на практике никто по кастам не грепает.

Ну да, у них разная степень "опасности", но насчёт "назывались касты так потому, чтобы по ним было удобнее грепать" — как-то я не уверен :) В C просто всего один вид каста, и с точки зрения C++ он "смешанный" — это фактически последовательная попытка static_cast, если не получилось, то тогда reinterpret_cast. А раз вид всего один, то и синтаксиса со скобочками достаточно (кстати, в C++ он никуда и не делся, он по-прежнему в стандарте). А когда у тебя много кастов, то их же надо как-то разделять между собой, вот ввели для них имена и разделили по именам.

Да, про это я в курсе, спасибо :)


Возможно, что это что-то вроде городской легенды, так же как то, что reinterpret_cast специально назван так длинно, чтобы его было дольше печатать и из-за этого меньше хотелось бы юзать.


Мне еще очень интересно, что вот эта идея — с разделением кастов на типы — как-то не очень прижилась в других языках. Обычно просто оператор as и все.

Вы имеете в виду managed языки? Ну там в «опасных» видах каста и нет необходимости в силу того, что там им не с чем было бы работать. Там, например, не нужно конвертировать pointer type в integral type и обратно, для чего частенько и применяется тот же reinterpret_cast. Но даже и там не всегда только один operator as, в том же C# есть и каст через (), он, в отличие от as, выбрасывает исключение при неудачном касте.

Я имею в виду вообще все языки, которые появились после С++, в т.ч. Go, D и Rust.
Во всех троих есть сырые указатели, но каст вроде бы всего один.


Так что шарп — скорее исключение (к тому же, касты в нем отличаются по другому признаку, не как в плюсах).

Ну ни Go, ни D и не работают во всех тех областях, в которых применяется C++, на них не пишут какие-то сильно системные вещи (не в последнюю очередь потому, что и там и там для управления памятью применяется GC). В rust для «особо опасных» случаев тоже есть фактически отдельный каст под названием transmute, но список «особо опасных» случаев там отличается от представлений C++.

Окей, но, скажем, const_cast (который вообще непонятно зачем нужен) или dynamic_cast вполне мог бы быть и там.

Ну в том же rust нет наследования в терминологии C++, а dynamic_cast применяется главным образом все-таки для «безопасных» путешествий по иерархии классов. Просто тот же static_cast специально сделан так, чтобы не вносить лишний runtime overhead при работе с RTTI, а если ты хочешь именно в рантайме там проверить, что вот этот объект, указатель на который там тебе передан в качестве аргумента, у тебя ТОЧНО именно вот этого класса, и, так сказать, ни классом выше, то вот сделали dynamic_cast. const_cast и в C++ действительно применяется довольно редко, я, наверное, по пальцам одной или максимум двух рук могу посчитать сколько раз я его видел в кодовой базе.
Ну не знаю, можно ли считать частный метод из некоей struct аналогом dynamic_cast, который в случае C++ вшит в язык, даже если эта struct входит в std.

P.S. Ну хотя transmute, строго говоря, тоже не конструкция языка, а интринсик из std. Так что да, наверное, можно.
Не читали Страуструпа?! Или он в Вашем понимании не плюсовик?..
Что-то я не припомню у него предложений грепать по кастам в целях исправления проблем. Ну, может, давно читал :)

P.S. А, это вы, наверное, про фразу «приведение в стиле C гораздо более опасно, чем указанные [именованные] операторы преобразования, потому что обозначения труднее обнаружить в большой программе»? Ну ладно, считается :)
да, я помню ваши «показательные» примеры

Это был мой показательный пример.
Вот вы в наших дискуссиях упорно путаете MRE (Minimal Reproducible Example) и баги в реальных проектах. Да, это круто, что вы понимаете, что держать один ресурс в двух умных указателях может быть больно. Но реальность делает еще больнее https://github.com/pybind/pybind11/pull/1139/files.


У проекта pybind11 (из названия сразу понятно, что проект нацелен на безопасный C++11) 5,956 звезд на гитхабе. Умеют эти люди в С++? Умеют. Допустили ошибку? Допустили. Помог бы им C++14? Нет. C++17? Нет. C++20? Нет. Rust? Да.

Ключевое слово "бы". А может и Rust не помог бы. Как здесь например:


https://github.com/rust-lang/rust/issues/56158


И в остальных случаях с проблемными реализациями std. И это ещё без пользовательского unsafe кода, а он в более-менее нетривиальных вещах будет обязательно. Я помню, автор way cooler (тоже отнюдь не новичок в расте) жаловался, что для того, чтобы взаимодействовать с wayland через wlroots, ему пришлось написать 11 тысяч строк растокода, и практически весь этот растокод занимался только управлением памятью с unsafe если не прямо в каждой строчке, то через строчку уж точно. Сколько там в этих 11 тысячах строк было потенциальных проблем — бог ведает. Так что с вашим "да" я бы погодил, это зависит.

> Ключевое слово «бы». А может и Rust не помог бы. Как здесь например:

Да, только вот по вашей ссылке первое же предложение это

> While trying out the experimental hashmap raw entry API

Что как бы подразумевает, что что-то может пойти не так. К тому же, этот API нужно ещё и активировать, указав #![feature(hash_raw_entry)]

:)
И что же, что experimental? Факт ошибки от этого никуда не делся. Была бы ошибка по какому-то менее используемому пути, так сказать — у неё были бы все шансы перейти из experimental в stable, а затем, может, и до CVE дорасти :) Да и issue вон открыт до сих пор.

И что Вы хотите этим сказать — что стандартная библиотека C++ или boost не имеют проблем с управлением памятью? Ну, это же не так. Но вот impact от того и другого — мне оценить сложно.

Я хочу сказать, что ответ "да(жирным), раст бы помог" — это некоторое… хм… преувеличение. Он бы, безусловно, помог… но только при соблюдении определенных правил. Как и C++.

Только в С++ этих правил 200+, а в Rust — только 5.


Вы нашли интересный пример, но он абсолютно нерепрезентативен.

Вы считаете, что ваш пример репрезентативен, а мой нет? Ну, хорошо, да будет так. CVE database для вас достаточно репрезентативна? Согласно ей, в rust-lang за последние 2 года было 5 CVE. Что у нас из аналогичных продуктов на C/C++ есть? Есть, например, clang, точно такой же фронтенд к llvm, кстати — в нем вообще ни одной CVE за последние 2 года, и всего одна за всю историю, в 2014 году (это, кстати, единственная CVE и в самом LLVM в целом, поскольку clang в CVE database выступает как часть LLVM). Возьмем GCC, в нем 8 CVE, но это за 20 лет (начиная с 2000 года), а за последние 2 года — всего 2 штуки. Я не вижу какого-то превосходства в плане безопасности продукта на rust перед аналогичными продуктами на C/C++, причем продуктами куда более сложными.

Проблема утверждений типа «а вот на rust такое написать было бы нельзя, компилятор бы не пропустил» в том, что на safe rust многое вообще в принципе написать нельзя в силу примитивности логики работы borrow checker'а. Как как-то говорил гражданин, которого тут все время банят (я не одобряю его манеру, но во многом из того, что он говорит, есть рациональное зерно) — «rust делится на safe rust, на котором вообще ничего написать нельзя, и unsafe rust, на котором что-то написать все-таки можно». Мысль утрированная, но по сути верная. Как только ты хочешь действительно написать что-то нетривиальное, ты просто вынужден тем или иным образом использовать unsafe, и у тебя полезут точно такие же баги, как и в C/C++. Ситуация усугубляется еще больше, если ты не имеешь опыта низкоуровневой работы с памятью, а многие питонщики/jsеры/goшники и так далее как раз сейчас и клюют на rust, как на инструмент, позволяющий им (в теории) получать что-то быстро работающее, но без «сложностей» C/C++ (как они думают). Страшно подумать, что будет, когда люди с подобным опытом (а точнее его отсутствием) возьмутся за написание unsafe кода (а они возьмутся рано или поздно).
Согласно ей, в rust-lang за последние 2 года было 5 CVE.

И опять предположения исходят из неправильных предпосылок.


  • Если в модуле нет unsafe, то API этого модуля невозможно использовать этот API без unsafe с возможностью триггернуть UB.
  • Если в модуле есть unsafe, и если этот модуль верифицирован, то API этого модуля невозможно использовать этот API без unsafe с возможностью триггернуть UB.
  • Если в модуле есть unsafe, но разраб напортачил с unsafe, то будут проблемы.

Из пунктов, указанных выше, следует, что в hash_raw_entry напортачили с unsafe и получили проблему. В С++ с этим проблем нет, потому что C++ не дает гарантий по API, ведь всегда можно обвинить конечного разработчика в неправильном использовании API. И таких багов, которые приводят к CVE, миллионы, их просто не репортят.


Страшно подумать, что будет, когда люди с подобным опытом (а точнее его отсутствием) возьмутся за написание unsafe кода (а они возьмутся рано или поздно).

Страшно, да не очень. Так как баги, подобные тем, что вы указали выше, это действительно проблема, сообщество пилит фреймворк для верификации кода. На текущий момент верифицированы большинство std примитивов: Box, Arc, Rc, Mutex, RefCell, Thread, Atomic… https://github.com/rust-lang/miri#bugs-found-by-miri


Вот вы можете сказать, что вы можете использовать C++ std::mutex и API не даст вам возможность выстрелить по памяти? А Rust это гарантирует. То же самое можно сказать про Box vs unique_ptr.


Вы считаете, что ваш пример репрезентативен, а мой нет?

Поэтому ваш пример является подтверждением моего утверждения: все ошибаются. Но никак не является подтверждением утверждения, что раст небезопасный.


rust делится на safe rust, на котором вообще ничего написать нельзя, и unsafe rust, на котором что-то написать все-таки можно

Ага. А еще есть люди, которые критикуют, а есть люди, которые пишут. В https://github.com/tox-rs/tox в 30к строк нет ни одного unsafe. И наша нода полгода не падает, тогда как сишные версии постоянно валятся на каких-то багах раз в день.

На текущий момент верифицированы большинство std примитивов: Box, Arc, Rc, Mutex, RefCell, Thread, Atomic… github.com/rust-lang/miri#bugs-found-by-miri

Это всего лишь старые добрые runtime тесты. Вы же не хотите сказать, что их раньше не было? Не потому ли их раньше не было, что «rust безопасен»? Щютка юмора, не обижайтесь.

А еще есть люди, которые критикуют, а есть люди, которые пишут.

Да, есть такие люди :)

Так код формально верифицировали: https://plv.mpi-sws.org/rustbelt/popl18/paper.pdf, а MIRI — следующий шаг от математиков. Сейчас из miri начинают переползать куски проверок, которые не дают компилировать код с явными багами внутри.

Я вообще фигею от этих вот сравнений по CVE. Ну, нашли уязвимость. Молодцы. Пофиксили. Молодцы. А сколько их еще не найденных и не подтвержденных в программах на С/С++? Я уж не говорю о том, что тогда стоит сравнивать какие-то похоже вещи — проекты примерно одинаковой сложности, выполняющие примерно одни задачи, написанные разработчиками примерно одинаковой квалификации. А то я так могут CVEшки и в программах на голанге наковырять, а потом ходить и трубить на каждом углу, что голанг — небезопасный язык.


P.S. ну, и давайте до кучи смотреть на https://www.cvedetails.com/product/3847/Microsoft-Visual-C-.html?vendor_id=26
Или думаете, что в MS тоже не профессионалы, а студенты пишут?

А сколько еще не найденных и не подтвержденных в программах на rust? И, если вы заметили, я специально выбрал аналогичные по функциональности проекты, а в плане сложности даже дал расту фору. Про квалификацию разработчиков сказать ничего не могу, возможно, в clang/gcc она и повыше, чем в rust-lang.
P.S. Ну давайте. 6 CVE за 16 лет (с 2004 года включительно), ноль не только за последние 2 года, но и за последние 9 лет.
Проблема утверждений типа «а вот на rust такое написать было бы нельзя, компилятор бы не пропустил» в том, что на safe rust многое вообще в принципе написать нельзя в силу примитивности логики работы borrow checker'а.

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


А, вспомнил. Два года назад писал очень требовательную к производительности ерунду, и обнаружил, что unsafeFreeze/unsafeThaw вместо freeze/thaw дают полпроцента к производительности. Но тут, кстати, сказывается отсутствие субструктурной типизации в хаскеле, и borrow checker бы помог.

Умеют эти люди в С++? Умеют.

спорное утверждение:
    static auto get(...) -> decltype(p.get()) {
      return p.release(); // предположение, что decltype(p.get()) всегда равен decltype(p.release())
    }


Rust? Да.

так это же ошибка выведения типов
Это уже новый код, специализированный для случая когда p передан через std::move() и при этом не copy constructible, который как раз и призван решить проблему. В случае get и release для unique_ptr тип действительно одинаковый. Почему автор патча в специализированной функции после стрелочки написал decltype(p.get()) — это вопрос, конечно. Вероятно, как говорил кот Матроскин, «чтобы не нарушать отчетность», т.е. чтобы сигнатура совпадала с неспециализированной версией.
А еще они имеют рантайм оверхед.
https://godbolt.org/z/mHcFeM

Кстати интересный пример. Как оно в c++ работает? Почему компилятор выкинул delete, потому что я не вижу heap аллокации тут.

Отвечу сам себе. Создание и удаление временного объекта для f(unique_ptr<T> p) лежит на стороне вызывающего. Поэтому в с++ реализация функции f не вызывает конструктор и деструктор. В расте происходит по другому, при f(p: Box<T>) ответственность за удаление объекта лежит на самой функции f поэтому мы видим dealloc в асм выхлопе. Естественно на всю программу количество new/delete в случае с++ и раст не изменилось (при условии что с++ компилятор выкинет delete nullptr для moved-out объекта). Далее было бы интересно подумать какие это дает плюсы и минусы для оптимизации сложной программы где есть много подобных вызовов (так же в случаях f(f(f(p)))). Хотя если всё заинлайнилось то по-идее разницы быть не должно.

Ага, а потом начинаются всякие std::string_view path, и падения в произвольных местах.

будьте добры, объясните пожалуйста, как вы в рамках своего мировоззрения можете объяснить существование софта на с++ который работает?

Мне кажется, это очень странный вопрос. Абсолютно так же, как и рабочий софт на любом другом языке — тестирование софта творит чудеса обычно.


Но в том же время, я считаю довольно самонадеянным расчитывать на то, что вы то всегда правильно будете работать с памятью, хотя даже с языками у которых есть GC у очень немногих получается. Шутка про то, что в программе на C++ всегда есть как минимум два разных segfault появилась не просто так.

Абсолютно так же, как и рабочий софт на любом другом языке — тестирование софта творит чудеса обычно.

так тестировать софт нужно даже если в нём заведомо нет ошибок памяти.

Можно вспомнить знаменитую табличку про стоимость исправления ошибки на разных этапах. Это один из кейсов PVS кстати, они по-моему не раз рекламировали "почему нужно прогонять постоянно, а не разово удостовериться что всё ок"

TL;DR
Отличная статья, но очень длинная. Прочитал первые три абзаца с удовольствием. Остальное оставил на вечер, когда можно будет спокойно почитать

Автор не запускал код, потому что для utf-8 надо считать количество символов так


line[start..location].chars().count()

Иначе форматирование ^ съедет.


Ксати почем для си не включили подсветку синтаксиса или это хабр чудит?

Ахаха. Отлично. Я тоже не запускал, дело в том что… Можно я просто поверю и исправлю?
Сишный синтаксис не естся хабрапарсером маркдауна. Сменил синтаксис на cpp, взлетело.
Спасибо за находку!

Изначальные поэмы вряд ли были из шедевров русской литературы, поэтому как раз в оригинале все отработало нормально;)

Only those users with full accounts are able to leave comments. Log in, please.