Comments 543
Да, оно.
У него есть похожий, "Серая дрянь" называется.
EDIT: Вот так, думаешь "о, точно у Кинга был же такой рассказ", гуглишь 15 минут — а тут уже написали =(
Уже хотел было Дедфуда спрашивать, что за рассказ такой и тут ваш комментарий… Спасибо :)
В команде же программисты разного уровня, как по мне нужно писать «средне», чтоб твой код потом и без тебя легко прочитали и разобрали, дописали, может исправили.
А такой код мне кажется половина людей даже не поймет.
Ну я в заключении написал, что даже по моему мнению это перебор.
С другой стороны, техника прикольная, полезно знать. Или это у меня уже стадия принятия?
2. Пишется «сложный» код вот почему: если программист познаёт в языке новую (для себя) фичу, выражающую что-то более общо, кратко и т.п., пусть и сложнее понимаемо, всегда будет соблазн её заюзать — «не зря же её придумали; остальные просто не шарят, вот как я до этого момента; подниму уровень своего кода до уровня тех крутых мужиков; да, не все осилят такой код, но проблемы неосиляторов меня уже не волнуют, пусть чувствуют кто в доме папа».
Нет, в проекты жуть не затаскивает (ну опять же зависит от личной градации, кому жуть а кому отл. решение).
Мне такое тоже нравится. К сожалению, в последнее время начал замечать, что даже после объяснений не до конца понимаю, почему именно так, а лезть глубже разбираться — фатально не хватает времени. Куда прикольнее становится скрафтить веранду на даче или прикинуться HT1621 и втиснуться в разрыв линии LCD, чего-то попутно меняя в законченном устройстве :)
Вы в примере хотите узнать, равно ли круглое съедобному.
Компилятор на это ругается. А вы ругаетесь на компилятор: он глупый, не знает про яблоки.
Учитывая сколько всяких равенств существует это своершенно корретная вещь.
В том же шарпе чтобы сравнить круглое со съедобным нужно будет использовать ReferenceEquals, а не обычный ==.
В общем, не вижу тут "бага", просто кто-то обожает всякие неявные касты и операторы которые ведут себя совершенно по разному в зависимости от контекста.
Ну так я и говорю что это наоборот плохо. Я был бы рад получить тут ошибку компиляции.
Чтобы сравнить указатели стоит вызвать функцию СравниУказатели(a, b), а не a == b. При вызове a == b нужно вызывать метод equal какого-нибудь интерфейса IEquitable, который очевидно в данном случае не реализован. То, что в шарпах оператор == работает не так же, как оператор + например это весьма печально.
Ну к сожалению это не так, и == как раз контекстно-зависимый. Для тех же структур он сравнивает значение, при реализации интерфейса IEquitable он переопределяется для опять же сравнения по значению. Поэтому я и говорю, что для сравнения по ссылке в сишарпе есть отдельная фунцкия, и было бы хорошо если бы в тс сделали так же. А то потом у вас в зависимости от боксинга может сравнение одних и тех же объектов разные результаты давать.
Проблема в том, что существует два вида сравнения:
- Два указателя указывают на один объект.
- Два указателя указывают на одинаковые объекты.
Если компилятор поменяет проверку, то возникшие баги будет непросто найти.
===
, а не ==
. Т.е. мы хотим узнать, одного ли типа эти объекты, и если да, то одинаковы ли они.Тьюринг-полная система типов и неразрешимый тайпчекинг — это вообще классика.
Но у вас там хотя б парсинг разрешимый, надеюсь. Не то что в плюсах.
А что не так с этим поведением? В общем-то случае терм некорректный. Чтобы чекнуть — можно всегда делать даункаст.
Просто настоящий программист никогда не ошибётся и два раза не напишет фри или не начнёт мутировать из двух потоков данные без синхронизации, и стандарты всегда знает наизусть. На у а если напишет и не знает — то что ж он забыл в профессии?
Ну в моем мире может и написать. Но на счастье в моем мире можно не вылезать из сейф оберточек, с которыми и проблем таких нет.
Ну мы переписали скала-сервис на хаскель, а затем и на раст, получили ускорение с минуты на обработку тяжелого запроса до двух секунд, и с 2-4гб потребления до 30мб, не вылезая из этого самого сейф кода. Жалко, что в вашем мире это невозможно.
Обычно в этот момент начинают говорить, что код неправильный, скала неправильная и вот это все, если бы не одно но — все 3 версии писал один и тот же человек, и раст ему предложил лично я полгода назад, а до этого он им даже не интересовался, тогда как на скале прод писал не один год. Такие дела.
Ну вот из внутренней презенташки результатов прототипирования. Сверху вниз: джава (писал другой разработчик, поэтому нерепрезентативно, там код откровенно фиговый был), скала, хаскель, раст.
Интересно ещё объём кода сравнить.
А память… Я как раз написал на хаскеле мелкую штуковину, которая склеивает два файла с временными рядами по десятку гигов каждый и считает всякую статистику по минутным корзинам (за день этих корзин 1440, что поменьше миллиарда точек в исходном файле). Изначально потребление памяти было вообще константным (и я не старался так писать, оно так само получилось), как только пришлось для некоторых статистик сортировать элементы в корзине, оно выросло до этак 30 метров в пике, что, учитывая наличие корзин с миллионом-другим элементов, вполне неплохо.
Было б любопытно переписать на плюсах и посмотреть бейзлайн, но лень.
Емнип около 20к строк на скале, и по 5к строк на хаскелле и расте. Но нужно понимать, что на прототипах еще не весь функционал реализован что был в скале.
А вот то, что в расте столько же строк вышло что и в хаскелле это конечно любопытный факт. Правда, стоимость этих строк во времени существенно выше оказалась. Но архитектура довольно сильно перекликается в том и другом случае. Основные отличия отсутствие лени в расте и мутабельность там, где она не мешает.
Не может быть. Бред какой-то
Это не бред, а реальная выдержка из прототипирования коммерческой компании, которая делает это не для пиара какого-либо языка, а чтобы снизить издержки. Я с вами поделился результатами, потому что мне они показались интересными. А дальше что делать с этой информацией решайте сами.
Тут интересно на сам код взглянуть. Как вообще адекватная реализация на Java может уступать в разы реализации на Scala?
Я вроде написал, что реализация на джаве была неадекватная.
А коммерческий код я показать вам не могу, уж извините.
— По итогу jar нашего продукта занимает всего 1 мб места.
— А почему тогда JVM весит 160+ мб?
— Это всё zero-cost абстракции.
А вы сколько времени на плюсы потратили?
У меня нередко бывало, что несколько дней с профайлером поднимали производительность на 2-3 порядка.
А примеры кода "до-после" остались? Как-то не верится, что без изменения алгоритмов можно выжать 2-3 порядка.
К сожалению, все случаи в моей практике, когда производительность была важна настолько, проприетарные.
Это, например, какой-нибудь random forest, когда начинается всякая акробатика с блочной обработкой данных, максимизацией переиспользования кеша и шины памяти, упаковкой структур, и так далее.
В целом с такими штуками JIT компилятор джавы вполне нормально справляется, генерируя плюс-минус такой же код как плюсовый компилятор (хотя временами все-таки чуть тупее). Так что отсутствие разницы неудивительно.
Не верю. Скала весьма близка к железу, если не обмазываться иммутабельностью. Скорость как у Java — т.е. обычно в ~1-3 раза медленнее, чем C.
Скорее всего, код был неоптимален, и с каждой версией улучшался.
Ну то есть — идиоматичный Хаскелль побуждает писать код оптимальнее Скалы, а идиоматичный Раст — оптимальнее Хаскелля?
Может быть. Но я сомневаюсь, что код был "идиоматичен", и практически уверен, что код не идентичен на разных языках, а на scala вдобавок написан весьма неоптимально.
Возможно, код на scala содержал много типичных scala-конструкций, которые работают очень медленно, и если не избегать конкретно каждой из них (например, о них не подозревая), код будет тормозить. Раз так в 50 по сравнению с оптимизированным кодом (близком к коду на C), и раз в 100 по сравнению с C.
Не знаю, как обстоят дела в хаскеле, но, возможно, в нём меньше таких неожиданностей. А раст компилируется примерно в те же инструкции, что и аналогичный код на С.
Также согласен с версией, высказанной в комменте ниже.
Может быть. Но я сомневаюсь, что код был "идиоматичен", и практически уверен, что код не идентичен на разных языках, а на scala вдобавок написан весьма неоптимально.
Код был везде идеоматичный. Для случая скалы это zio/monix/местами шейплес. Вполне такая обычная скала.
В хаскелле обычный хаскель код с MTL и расставленными атрибутами а-ля UNPACK
.
В расте то же что и в хаскелле, но где нужна была мутабельность была явная мутабельность.
Так что учел возможные недостатки и недоделки, в итоге заодно получился рефакторинг :)
Ну там как бэ не просто "ну мы тут переписали", а анализ проводился. Растовый компилятор например практически всё очень хорошо заинлайнил, 5000 строк почти все собрались в один гигантский main, в релизе только ошметки некоторых функций остались.
Ну а разница с хаскеллем заключается в большей части в том, что раст всё делает на стеке. Соответственно и выигрыш весь заключается в том что в куче ничего почти чистить не надо, гц не жрет проц, и вот это все.
После написания версии на расте, хаскельную тоже дописали, и получили прирост процентов 20, используя знания полученные при написании прототипа (расставление префетчей и likely на условиях), но и все.
Переписывание одной и той же программы на одном и том же языке не даст вам никакого прироста, если вы специально не переписываете ради прироста. У нас в прототипе такой цели не стояло.
Хабр контекстно-зависимый, прям автомат с памятью. Любо-дорого читать!
Профессионалы отличаются тем что пишут простой и скучный код который поймет даже тот кто не знает язык. Чем ниже уровень программиста, тем запутанней и выпендрежней становится код.
ЗЫ. Писать простой код — это искусство ;)
Профессионалы отличаются тем что пишут простой и скучный код который поймет даже тот кто не знает язык
рано или поздно будет выбор «больше кода на простых конструкциях» (сложнее читать из-за объема) или «меньше кода на конструкциях посложнее» (сложнее читать из-за конструкций). И надо знать золотую середину.
Как и везде, крайности всегда плохо.
Но писать
return isset($arr['val']) ? $arr['val'] : null
вместо return $arr['val'] ?? null
на проекте с php7 по моему через чур.Или ипользуют xml/html для разметки когда есть markdown ( как хабр :) )
PS. Да, иногда бывает просто по привычке вербозят код.
Профессионалы отличаются тем что пишут простой и скучный код который поймет даже тот кто не знает язык.
Это не всегда является значимым критерием при выборе конкретного решения задачи.
Например, люди иногда пишут библиотеки, и оказывается полезным уметь всякие неочевидные финты. Иногда — чтобы сохранить чистый и понятный для новичка в языке API библиотеки, что особенно забавно.
… афигеть… просто нет слов.
Я уже даже перестал нервно смеяться, когда слышу что-нить вроде «С/С++? Да, чо там, простые же языки»…
Не тот язык назвали brainfuck, ох не тот… Хотя всё равно люблю его, но это какой-то капец
int x = sizeof(int) > -1;
Чему равен x? Даже на неявных преобразованиях базовых типов можно споткнуться.
Я дилетант, но мне думается, ответа "точно не нулю" на практике достаточно
Для дилетанта да, но вот только это неправильный ответ :)
Неявные преобразования зло, на них не даже, а практически всегда споткнуться можно. А вот на явных даже.
size_t по стандарту unsigned, а вот его близнец ssize_t — signed.
Вот разборка этого кейса:
https://stackoverflow.com/questions/26957049/return-value-of-sizeof-in-c
В моей практике, человек который так написал, не пройдёт код-ревью скорее всего. Потому что зачем так?
После получения от него объяснений зачем и понимания, что не будет ни undefined behavior где-то дальше по коду, ни проблем с переносимостью — можно оставить. С комментариями для потомков, кстати. В данном конкретном месте.
Я вас удивлю, но этот пример переносим и не содержит UB. Значение x всегда будет 0. Выражение uint32_t x = -1
эквивалентно uint32_t x = UINT32_MAX
, и этот способ используется для переносимого (!) заполнения всех битов в 1. С другой стороны, обратная операция (знаковое переполнение) уже является UB.
https://github.com/python/cpython/blob/v3.8.0/Modules/socketmodule.c#L2560
https://github.com/python/cpython/blob/v3.8.0/Python/ast_opt.c#L130
https://github.com/postgres/postgres/blob/REL_12_0/src/backend/utils/mmgr/dsa.c#L1234
Как сейчас принято CRC считать, раз переполнение == UB?
Кстати, может быть знаете, из каких соображений сделали аж UB, а не platform specific?
Хм, заглянул в раздел определений стандарта 13ого года (какой быстрее нагуглился) слегка прифигел от терминологии (undefined behavior и unspecified behavior). Зачем ввели базовые термины, которые в сокращениях совпадают? Не понимаю.
Уже много лет преследует ощущение, что язык пытаются убить, делая его всё более идиотским и переусложненным на ровном месте. А программисты сопротивляются и пишут на нем «вопреки», т.к. привыкли. А небольшой части это даже нравится — ибо можно повыпендриваться ). Но я, скорее всего не прав — просто молодость прошла и учиться стало лениво.
Тем не менее, если эта штука приедет на код ревью, ей понадобится ответ на вопрос «зачем именно так».
На любом языке можно наворотить, если позволить «мета программирование», так что волосы зашевеляться где угодно )
будет уметь примерно все тоже самое, но радикально уменьшит сложность за счет выкидывания всех исторически-сложившихся несуразностей и упрощения синтаксиса.
Именно такие цели ставили создатели D.
К сожалению, С++ у него что-то никак убить не получается, он влачит довольно жалкое существование.
Лично мне нравятся очень многие вещи в D, но он оставляет впечатление недостаточно проработанного языка.
Да что все твердят про сложность раста?) Ей богу, если сравнить с С++, то раст в разы проще синтаксически.
Мне он не показался чем-то неожиданным после плюсов. Скорее наоборот. Пока пишешь safe-код, ощущение, словно пишешь на С++ из которого убрали боль. А unsafe вызывает боль не сильно больше, чем аналогичный код на плюсах.
К тому же, безопасность работы с памятью — это устранение, ну… наверное 95% ошибок при программировании на С++?
К тому же, кроме работы с памятью, Rust предоставляет:
1) Кучу приятного синтаксического сахара вроде match expressions или оператора? для Result и Option (потенциально для любых монад в будущем).
2) Система сборки без боли, отчаяния, криков ужаса, жуткого легаси и невероятной магии. Каждый раз когда думаю о сборке проекта в С++, глаз начинает дёргаться.
3) Пакетный менеджер из коробки
4) Переключение тулчейна без боли и отчаяния
5) Никаких ADL, SFINAE, аргументов по умолчанию, анонимных неймспейсов, static thread_local volatile переменных, неявных целочисленных и не только кастов, strict aliasing-а, noexcept, rvale, lvalue, xvalue, богомерского std::string, ещё более богомерского std::wstring, 20-ти видов инициализации, include-файлов, и бог знает чего ещё...
Нет, я люблю С++. Хотя бы и за то, что после него можно смотреть с улыбкой (и лёгким налетом грусти) на все жалобы в стиле "Python 3.8 стал таким сложным"…
По итогу: раст и надёжнее, из-за того, что убрали почти все способы отстрелить себе ногу. И проще, потому что не нужно знать Make, CMake и ещё с десяток менее популярных или даже самописных систем сборки. В целом, раст наверное даже не сильно медленнее.
Язык этот однозначно ещё не продакшн-рэди, по крайней мере до стандартизации ABI. Но потенциал огромный.
В целом, раст наверное даже не сильно медленнее.
В этот момент я подумал "хехе" и полез искать статью. Потратил минут 10 но нашел. Рекомендую к прочтению. К после прочтения поймете, почему такая реакция :)
Прочитаю инфу по ссылке попозже, пока что отвечу про часть:
потому что зачем тогда вообще раст, если все будет делаться через кучу совершенно лишнего unsafe кода, который просто дергает C/C++ библиотеки
В каком смысле зачем? На расте можно написать safe обертки над этими библиотеками, как миникм5 и уже без страха писать остальную программу. Если поддерживать такую обёртку дорого и сложно, можно тогда и переписать библиотеку, не вижу с этим проблем.
Мне откровенно непонятно стремление выбросить С++ и переписать всё на раст. Тысячи человеколет на С/С++ потрачены зря что ли?). Раст потому и крут, что довольно "легко" интегрируется с Си.
А мне непонятно стремление писать на раст ради того, чтобы писать на раст. "Без страха" — не аргумент, писать на C++ тоже никто не боится, а трата времени и денег на написание бесполезных "safe оберток" вокруг C++ кода бессмысленна — проще сразу писать на C++ и само приложение. Про "лёгкость интеграции" раста уже писал, по-моему, Alexey2005, в одной из тем про раст, это совершенно лишний источник головной боли, который можно оправдать, если C/C++ кода в проекте будет, скажем, 20%, но не подавляющее большинство.
Мне откровенно непонятно стремление выбросить С++ и переписать всё на раст.Переписывать не надо. Новые проекты лучше не на С++ начинать. В будущем.
если C/C++ кода в проекте будет, скажем, 20%, но не подавляющее большинствоА зачем Расту С++? Одного С вполне достаточно будет.
Странный какой-то подход, если C++ хорошо и органично подходит для проекта, глупо его не использовать. Скажем, проект по моей ссылке выше написан на C++ и удовлетворяет всем требованиям и по производительности, и по кросс-платформенности, и по функционалу, почему его не надо было "начинать на C++"? Из чисто идеологических соображений? Так это же глупо.
А зачем Расту С++? Одного С вполне достаточно будет.
Ну ОК, никто вас за язык не тянул, достаточно так достаточно. Ознакомьтесь с приложением по моей ссылке выше и ответьте на вопрос, на каком инструментарии вы бы его делали при условии «80% раста, 20% C, никакого C++, так как он не нужен». Все требования к производительности, поддержке платформ и прочему, естественно, сохраняются.
Я не эксперт в таких вещах, но разве переход на поголовные раст-обёртки не выведут легаси на совершенно непостижимый уровень? Боюсь, что прогресс не остановится на Расте и рано или поздно на рынок таки выйдет язык, который должен будет серьёзно уделать как минимум плюсы (вероятно, и Раст, раз они метят в одну нишу), язык, который все до сих пор именуют «убийцей плюсов». К тому моменту, если переходить на написание Раст-кода над С++ обёрнутого, количество людей, которые захотят переписать легаси из С++ + Раст над ним, будет в разы меньше тех, кто переписал/переписывает Сишное легаси.
Писать десктоп-приложения на расте на текущий момент не на чем, надо брать Qt, как у вас и сделано.
Впрочем, десктоп-приложения в 2019 году мало кому нужны поэтому это не особо серьезная проблема.
"Без страха" — не аргумент, писать на C++ тоже никто не боится,
Ой ли? Или не боятся по ногам стрелять? Ну так страх это вещь полезная, как и боль, можно сказку про сгоревших человечков которые не тушили огонь потому что им не было больно вспомнить.
а трата времени и денег на написание бесполезных "safe оберток" вокруг C++ кода бессмысленна
А это другой вопрос. Писать на расте и экспортировать сишное апи действительно неудобно. Но это совсем другая разница, как говорится.
"Потребности в колбасе на сегодня нет", как в старом анекдоте? :) Ну ОК.
Нет, я просто честно говорю про два факта:
- для десктоп-разработки на раст сегодня ничего предложить не может
- десктоп-разработка на сегодняшний день не очень востребованна, можно по вакансиям глянуть. Тот же сишарп в котором равноценно есть десктоп WPF и в больше чем 90% вакансий ищут именно веб.
Ну понятно, что веб разработки сейчас много в процентном отношении, сайтики, онлайн-сервисы, вот это вот все. Но задач, где веб-сервис не годится, тоже хватает, их тоже надо делать. Впрочем, Qt и в веб заходит потихоньку, в 5.13 появилась поддержка wasm как одного из таргетов, пока это technology preview, но процесс идёт.
Ну, рецепт простой, для десктопа в 2019 году нужно брать плюсы, как ни странно :)
Заодно хотелось бы уточнить, т.к. я пока не понял
В отличие от D у раста позиционирование совершенно четкоеА какое оно, можно оф.ссылку?
Можно — на главной странице https://www.rust-lang.org/
From startups to large corporations, from embedded devices to scalable web services, Rust is a great fitА это нифига не четкое позиционирование…
Везде, где вам нужна производительность С++ с безопасностью высокоуровневых managed-языков, либо bare metal/embedded/...
То есть язык не может позиционироваться как "общего назначения", я правильно понимаю Вашу мысль?
Я и пытался выяснить эти цели.
для десктопа в 2019 году нужно брать плюсы
Скорее JavaScript и Electron.
А C++ — для игр разве что, и прочего полноэкранного графического софта. И то, во многом потому что Unreal Engine и прочие Unity не так-то быстро переписать на Rust.
Скорее JavaScript и Electron.
Ыыы. У меня прямо сейчас Skype на десктопе полгига RAM отжирает, больше отжирает только Хром, да и то, в нем открыто где-то с десяток вкладок, а отжирает он только в три раза больше. Я еще помню времена, когда Skype жрал на порядки меньше, а умел в разы больше. Electron is Cancer ©.
Язык этот однозначно ещё не продакшн-рэди, по крайней мере до стандартизации ABI.
Не то что плюсы.
Да хз, я сам из этой "касты") Мне нравятся плюсы, нравится их просто невероятно мозговыносящие конструкции на шаблонах и макросах. Я испытываю искреннее удовольствие от приведённых в статье конструкций, НО! Это чисто академическое удовольствие, как удовольствие от решённой головоломки.
В продакшн коде помимо написания и разбора кода ещё нужно ещё собирать, конфигурировать, интегрировать, деплоить, писать и запускать тесты и в общем-то решать бизнес-задачи. На плюсах это делать не всегда приятно в итоге, из-за отсутствия нормальных инструментов для решения обозначенных задач.
Куча народа пишут игры на Rust. Правда пока нет серьезного движка то ААА на нем мы наверное пока не увидим, но вот все остальное — пожалуйста.
Правда пока нет серьезного движка то ААА на нем мы наверное пока не увидим
вы же представляете сколько человекочасов потрачено на какой-нибудь UE? Даже если предположить что unreal перейдут на раст сегодня, забив на поддержку, и даже если они будут пилить движок в 5 раз быстрее чем на плюсах, продукт которым можно будет пользоваться мы увидим не раньше чем лет через 5 плюс еще лет 10 обкатки на реальных проектах.
Ну, что не надо делать идолов, это да, но с остальным я не согласен:
1) Все остальные языки используют наработки С++, чтобы упростить написание прикладных приложений. Rust же позиционируется как системный ЯП, а потому в сущности предлагает делать всё то же самое, что С++, только "правильно". Остальные попытки победить С++ всегда имели какой-нибудь фатальный недостаток, вроде GC в D или непродуманной системы типов и все такое.
2) Жрать что дают скучненько и вгоняет в депрессию. Плюс, команду можно и дообучить, а кривизну плюсов уже не выправить. В конце концов, мы же о С++ говорим. Если человек осилил плюсы и инфраструктуру вокруг них, то разобраться с Rust не составит труда.
А вот «жрать что дают скучненько» — тут важен разумный компромисс между решением конкретных задач и всем вот этим дообучением команды и прочим… Так-то и сайты на плюсах кропать никто не мешает интересненькие со встроенным сервером например, но зачем?
К тому же, безопасность работы с памятью — это устранение, ну… наверное 95% ошибок при программировании на С++?
Вот сильно не уверен. Не скажу за всех, но лично мне очень кажется, что страшилки про «небезопасность работы с памятью в С++» сильно преувеличены. Уже давно придумана масса вполне надёжных техник, которые от этой проблемы избавляют. По крайней мере лично в моём опыте я даже не вспомню, когда в последний раз меня это кусало… Может разве что лет 20 назад, когда только учился, да и то не факт, поскольку переход на С/С++ после ассемблера очень облегчён.
Но вот всякие хитропопые undefined behaviour или х10 ускорение после разборки с data aliasing и т.д. и т.п. — вот это периодически случается и сейчас. Про пример в посте вообще не вспоминаю, — это ж поседеть можно, пока допрёшь что за фигня, если не сталкивался.
Главная беда, С++, имхо абсолютно не работа с памятью (она вполне безопасна и комфортна после некоторого уровня понимания), а адски замороченный стандарт с «кучей условий мелким шрифтом, сносками и звёздочками», что как раз демонстрирует пример этого поста… Но главная беда этой главной беды в том, что она, скорее всего, практически не излечима, просто потому, что заменив сложность на простоту почти всегда неминуемо проиграешь в конечном итоге в эффективности, поскольку вся эта сложность сделана с единственной целью — позволить компилятору выжимать максимум возможной эффективности из кода.
Не скажу за всех, но лично мне очень кажется, что страшилки про «небезопасность работы с памятью в С++» сильно преувеличены
Тоже не скажу за всех, но Segmentation Fault на моей памяти самая частая причина почему падает плюсовое приложение.
Уже давно придумана масса вполне надёжных техник, которые от этой проблемы избавляют.
Никто и не отрицает, что на С++ нельзя писать хорошо. Проблема в том, что устроить состояние гонки в плюсах очень и очень просто. Получить UB на ровном месте из-за нарушения strict aliasing — еще проще. Собственно в этом и разница между С++ и Rust. На С++ сложно писать правильно, нужно следовать куче практик из Core Guidelines. На Rust нужно проделать столько же усилий, но только в обратную сторону — чтобы начать писать неправильно. И все эти места еще явно обозначить как unsafe.
Но вот всякие хитропопые undefined behaviour или х10 ускорение после разборки с data aliasing и т.д. и т.п. — вот это периодически случается.
И всё это решается Rust-ом просто на уровне правил языка. Получить UB сложно, получить нарушение strict aliasing вообще невозможно.
Главная беда, С++, имхо абсолютно не работа с памятью (она вполне безопасна и комфортна после некоторого уровня понимания), а адски замороченный стандарт с «кучей условий мелким шрифтом, сносками и звёздочками», что как раз и демонстрирует пример этого поста…
Вот это как раз-таки не сильно большая проблема. Конструкции из статьи в реальном коде встречаются чуть реже, чем раз в год.
вся эта сложность сделана с единственной целью — позволить компилятору выжимать максимум возможной эффективности из кода.
А вот это можно обозначить за главную проблему современного стандарта С++ :) На нём приходится писать так, чтобы удобно было компилятору, а не тебе. Не сказал бы, что в Rust нет такого (чего только стоит секс с borrow checker-ом зачастую), но если в Rust эти потуги вознаграждаются в итоге более строгой архитектурой и меньшим количеством хаков, то в С++ обычно выходит с точностью да наоборот)
Тоже не скажу за всех, но Segmentation Fault на моей памяти самая частая причина почему падает плюсовое приложение.
1. разных возможностей уронить процесс вообще не так уж и много, и неправильная работа с памятью — это самая широкая из них. Поэтому не удивительно, что программы с ошибками падают чаще всего именно на памяти. Ну не ud2 же им падать?
2. любая безопасная работа требует чёткого понимания когда конкретно с чем конкретно работаем. Тогда пиши хоть на асме — не будет никаких проблем. Юниксы и прочие Линуксы, написанные на самых небезопасных С/С++ с годами аптайма тому пример (Винда диапазона NT4.0-2000-7 тоже совсем не так дурна на самом деле, как её любят представлять, и там точно микс С/С++ даже в kernel mode драйверах, но никсы в примере канонiчнее, конечно).
3. Программер может купить гарантии безопасности памяти с помощью языка, но это возможно сделать исключительно продав весьма много дополнительной ран-тайм нагрузки.
Отсюда простой вывод —
- разных возможностей уронить процесс вообще не так уж и много, и неправильная работа с памятью — это самая широкая из них. Поэтому не удивительно, что программы с ошибками падают чаще всего именно на памяти. Ну не ud2 же им падать?
Ну в сишарпе самая частая ошибка — падение с NullReferenceException. Вторая — KeyNotFoundException.
- любая безопасная работа требует чёткого понимания когда конкретно с чем конкретно работаем. Тогда пиши хоть на асме — не будет никаких проблем. Юниксы и прочие Линуксы, написанные на самых небезопасных С/С++ с годами аптайма тому пример (Винда диапазона NT4.0-2000-7 тоже совсем не так дурна на самом деле, как её любят представлять, и там точно микс С/С++ даже в kernel mode драйверах, но никсы в примере канонiчнее, конечно).
Остается вопрос стоимости написания такого безопасного приложения. Один вопрос когда у вас сениоры с 10 годами опыта должны еще друг за дружкой следить и валгринды гонять на регрессе. И другое когда у вас джун с 1-2 годами опыта выдает продукт с теми же характеристиками.
- Программер может купить гарантии безопасности памяти с помощью языка, но это возможно сделать исключительно продав весьма много дополнительной ран-тайм нагрузки.
А вот это миф. Собственно, раст хороший пример, когда с безопасностью можно еще и выиграть в производительности. Наглядный пример — бесплатные ссылки & вместо вполне себе платных shared_ptr. Безопасность и производительность абсолютно ортогональные вещи. То что в некоторых языках решают пожертововать одним ради другого не означает, что это основной закон мироздания.
Наглядный пример — бесплатные ссылки & вместо вполне себе платных shared_ptr.
А зачем использовать shared_ptr там, где и в плюсах можно использовать ссылки?
Тем, что & проверит множественность владельцев, то что никто не мутирует в это время нашу ссылку, то что она указывает на живой объект и не инвалидируется, в общем вот это все. А с ссылками никаких гарантий нет.
Наглядный пример — бесплатные ссылки & вместо вполне себе платных shared_ptr.
сколько раз вам нужно написать что в плюсах «вместо растовых ссылок» используются ссылки, а не shared_ptr?
Сколько угодно. Получается там, где в расте будет безопасный код на плюсах имеем кучу потенциально стреляющего по ногам кода.
В C/C++ когда-то решили, что за корректность семантики тех или иных действий на той или иной конкретной платформе отвечает программист и назвали это неопределенным поведением. Решение оказалось удачным, что показывает объем реально работающего кода на этих языках.
Прошло 40 где-то лет и в Rust на основании накопленного опыта придумали, как в C ограничить 3 вида UB в самом языке и еще несколько на уровне библиотеки и делать это во прямо во время компиляции, не дожидаясь рантайма. Смержили результат с OCaml'ом и презентовали публике. Как по мне — норм, пожалуй, лучше, чем то, что было. Хотя куча прелестей все-равно осталось.
Но всерьез напирать на какую-то мифическую безопасность одного или, наоборот, опасность другого…
Для раста формально доказано, что в сейф подмножестве вы не получите УБ. Если вы доверяете ансейфу в стд (он сейчас тоже валидируется формально, к слову), то получается что уб в ваше программе невозможно получить. Равно как и разыменование нуллов, и прочие неприятные вещи. Как по мне весьма существенная гарантия.
Вы вообще не получите UB. Ни подмножества, ни надмножества. Без ансейф раста получить уб невозможно.
Dereferencing null, dangling, or unaligned pointers
Reading uninitialized memory
Breaking the pointer aliasing rules
Producing invalid primitive values:
dangling/null references
null fn pointers
a bool that isn't 0 or 1
an undefined enum discriminant
a char outside the ranges [0x0, 0xD7FF] and [0xE000, 0x10FFFF]
A non-utf8 str
Unwinding into another language
Causing a data race
Это серьезный прогресс, но и не исчерпывающий список.
let array_idx = 1e99999f64 as usize; // ой, не влезло, array_idx не определен
let val = some_array[array_idx]; // упс
как-то так. Насколько я вижу, соответствующий issue до сих пор не закрыт.
Ну это ICE, тут вопрос другой. От багов компилятора ни один язык не защитит.
Не то чтобы это баг компилятора, просто обычный случай протекания абстракции. Перед конверсиями такого рода в общем случае требуются проверки, но на каждый чих их делать дорого, отсюда и вот эта раскоряка в лице этого флажка. Когда говорят "математически доказано", "проверено электроникой" и так далее, забывают, что математическое доказательство относится к МОДЕЛИ, а модели никогда не отражают суровую реальность идеально.
Попробовал проверить это локально, не ругается ли клиппи, но у меня даже собрать не вышло: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=b9d3bb21be0d1c2a5cfbc0a46749fb06
Когда говорят "математически доказано", "проверено электроникой" и так далее, забывают, что математическое доказательство относится к МОДЕЛИ, а модели никогда не отражают суровую реальность идеально.
Я предпочитаю исходить из того, что в случае возникновения проблемы она не в компиляторе/иде/процессоре, а в моем коде. Поэтому подобные возможности уменьшить их объем уменьшают почти в той же пропорции, что и общий объем пространства рисков.
Ну тут бага в компиляторе, очевидно. https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=292978828b9586bd1a3a5ea80fdc2692
Я же говорю — это не бага, а неизбежное зло. В таких конверсиях компилятор либо должен вставить ряд runtime проверок и зарулить производительность в ряде сценариев в минуса, либо пойти по пути C/C++ и оставить добавление проверок в каждом конкретном случае на усмотрение программиста (сиречь допустить UB). Это цугцванг.
Достаточно запретить такую конверсию, пусть разраб либо сам через ансейф реализует нужное ему поведение, либо даже не знаю что сделать.
Делать это UB смысла нет, потому что результатом все равно не получится воспользоваться.
Получится, если при такой конверсии float/double влезут в целевой целочисленный тип. Если разработчик уверен, что влезет — все нормально. Если не влезет — увы, будет печаль. Насчёт вынести это в unsafe — ну хз, выглядит, честно говоря, как недавняя попытка экопротестунов закрыть деревянными палетами дизельный генератор :)
Вопрос не в этом. Нужно понять, имеет ли разумное значение подобный каст. Если да, то сделать его в компиляторе, если нет — запретить.
В текущем виде каст смысла не имеет. Мало того что мы кастуем из флоата в инт в значение другой битности, так еще и значение не принадлежащее домену типа.
У меня в хаскеле есть минимум три функции с сигнатурой [которая после мономорфизации выглядит как] Double -> Int
, называются floor
, ceil
и round
. Какую из них надо выбрать под такой каст и почему?
Сколько угодно. Получается там, где в расте будет безопасный код на плюсах имеем кучу потенциально стреляющего по ногам кода.
к черту «потенциально», давайте «фактически»
Окей, кучу фактически стреляющего по ногам кода.
Окей, кучу фактически стреляющего по ногам кода.ну уж нет, не отделаетесь. Вы написали:
Наглядный пример — бесплатные ссылки & вместо вполне себе платных shared_ptrДавайте-ка доказывать, конкретным примером на расте, использующим только ссылки так, чтобы нельзя было написать аналогичный код на плюсах ограничившись ссылками
…
Сколько угодно. Получается там, где в расте будет безопасный код на плюсах имеем кучу потенциально стреляющего по ногам кода.
Ну вот давайте такой пример:
fn f<'a>(x: &'a i32) -> impl Fn() -> i32 + 'a {
move || *x
}
fn main() {
let x = 5;
let y = f(&x);
println!("{}", y());
}
На плюсах:
#include <memory>
#include <iostream>
#include <functional>
std::function<int(void)> f(std::shared_ptr<int> x) {
return [&]() { return *x; };
}
int main() {
std::shared_ptr<int> x(std::make_shared<int>(4));
std::function<int(void)> y = f(x);
std::cout << y() << std::endl;
}
При этом даже шаред_птр не очень спасут, потому что вот это будет ошибкой компиляции:
fn f<'a>(x: &'a i32) -> impl Fn() -> i32 + 'a {
move || *x
}
fn main() {
let y;
{
let x = 4;
y = f(&x);
}
println!("{}", y());
}
А вот это — нет:
std::function<int(void)> f(std::shared_ptr<int> x) {
return [&]() { return *x; };
}
int main() {
std::function<int(void)> y(nullptr);
{
std::shared_ptr<int> x(std::make_shared<int>(4));
y = f(x);
}
std::cout << y() << std::endl;
}
На плюсах:даже с std::shared_pt без UB не смогли, ок, пишите на расте от греха подальше. Но всякую чушь нести не надо ибо аналогом на плюсах будет:
#include <iostream>
#include <functional>
auto f(int& x) {
return std::function([&] { return x; });
}
int main() {
int x = 4;
auto y = f(x);
std::cout << y() << std::endl;
}
даже с std::shared_pt без UB не смогли
Так про это и речь, не?
Если ты не можешь забить гвоздь в стену, а вместо этого попадаешь по пальцу, это не повод винить молоток.
А ещё это повод сказать, что молоток уступает тому инструменту, за который невозможно взяться так, чтобы попасть по пальцам.
Так про это и речь, не?во-первых, с него станется и специально эту багу посадить. Во-вторых, речь про другое. PsyHaSTe пытается доказать что в плюсах не существует способа добиться производительности rust. А способ есть, всегда, надо лишь головой думать, что и как ты делаешь. Если же хочется не думать, а клацать пока не соберется «корректная» программа, тогда да, раст безусловно лучше.
Так про это и речь, не?речь не про это. Коллега пытается доказать, что раст быстрее плюсов потому что на плюсах невозможно написать корректную программу не теряя в производительности. Это ложь, а (личная) некомпетентность не является аргументом в пользу невозможности.
Ну так это ведь не совсем аналог, если я так понимаю пере выводом написать x = 5
то выведется совсем не то.
Проверить, к сожалению, не могу, потому что ваш пример у меня не компилируется: https://rextester.com/XRE91350
пишите на расте от греха подальше
Так я и сделаю.
Э, нет, не надо тут strawman использовать. Я сказал, что производительность и безопасность ортогональны, и не вам не всегда обязательно жертвовать одним в пользу другого.нет, вы сказали:
Наглядный пример — бесплатные ссылки & вместо вполне себе платных shared_ptr.что я собственно и прошу доказать.
Ну так это ведь не совсем аналог, если я так понимаю пере выводом написать x = 5 то выведется совсем не то.мой пример отлично компилируется актуальной версией компилятора и стандарта. А вот «написать перед выводом x = 5» в расте не получится, из-за заимствования. Зачем вы спорите о языках ни в одном из которых вы не компетентны?
Проверить, к сожалению, не могу, потому что ваш пример у меня не компилируется: rextester.com/XRE91350
что я собственно и прошу доказать.
Я показал по крайней мере один пример. Этого достаточно для утверждения, что бесплатная безопасность — бывает.
мой пример отлично компилируется актуальной версией компилятора и стандарта. А вот «написать перед выводом x = 5» в расте не получится, из-за заимствования. Зачем вы спорите о языках ни в одном из которых вы не компетентны?
Я прекрасно знаю, что оно не компилируется. Примерно то же поведение я хотел бы в плюсах. Если не ошибку компиляции, то хотя бы эксепшн в рантайме.
Я показал по крайней мере один пример.этот пример, даже будь он написан без ошибок, абсолютно ничего не доказывает
Этого достаточно для утверждения, что бесплатная безопасность — бывает.я просил доказать не это. Я просил привести пример кода на расте, не выразимого в плюсах без доп. накладных расходов. Этого вы сделать не можете и даже не понимаете почему.
Я прекрасно знаю, что оно не компилируется. Примерно то же поведение я хотел бы в плюсах. Если не ошибку компиляции, то хотя бы эксепшн в рантайме.так а если я хочу поменять значение переменной на которую существует ссылка? Использовать «весьма небесплатные» reference-counted обертки над ссылками?
я просил доказать не это. Я просил привести пример кода на расте, не выразимого в плюсах без доп. накладных расходов. Этого вы сделать не можете и даже не понимаете почему.
Я уже показал. Либо вы будете делать обертки, которые проверят это, либо потеряете в безопасности.
Другой пример — Option<NonZero<u32>>
какой-нибудь.
так а если я хочу поменять значение переменной на которую существует ссылка? Использовать «весьма небесплатные» reference-counted обертки над ссылками?
Значит придется перехотеть и выразить свои мысли иначе. А то напоминает, как товарищь eao спрашивал "а как мне достать значение из опшна без проверки, если ну очень надо".
Я уже показал. Либо вы будете делать обертки, которые проверят это, либо потеряете в безопасности.а. ваше утверждение было не про безопасность
б. если не думать головой что ты делаешь никаких оберток не напасешься
Другой пример — Option<NonZero<u32>> какой-нибудь.Написать его аналог на плюсах несложно. Но никто не пишет… наверно потому, что не нужен?
Значит придется перехотеть и выразить свои мысли иначенапример?
А то напоминает, как товарищь eao спрашивал «а как мне достать значение из опшна без проверки, если ну очень надо».а если я могу доказать что в этой ветке кода вышеупомянутый опшн никогда не будет пустым, зачем мне его перепроверять? Или так работает та самая zero cost безопасность которую вы пытаетесь доказать? Пока что только опровергаете
а. ваше утверждение было не про безопасность
То есть в фразе "производительность и безопасность ортогональны, и не вам не всегда обязательно жертвовать одним в пользу другого" нет ничего про безопасность? Ясно-понятно.
б. если не думать головой что ты делаешь никаких оберток не напасешься
Верно, но чем меньше нужно про это думать, тем лучше.
Написать его аналог на плюсах несложно. Но никто не пишет… наверно потому, что не нужен?
Зиро-кост абстракцию такого вида без поддержки компилятора вы не напишете. А если мы затащим таким образом одну, вторую, третью фичи раста получим раст. Тогда что от плюсов останется?
А то, что нинужна — ну конечно, настоящий программист же всегда помнит, где у него что на нулл проверилось и в каком поле нолик означает отсутствие значение, а в каком — нет.
например?
Попытка модифицировать значение на которое кто-то держит ссылку — это ошибка. Можно сделать арены, чтобы ссылок не было, можно убрать мутабельность, вариантов много.
а если я могу доказать что в этой ветке кода вышеупомянутый опшн никогда не будет пустым, зачем мне его перепроверять? Или так работает та самая zero cost безопасность которую вы пытаетесь доказать? Пока что только опровергаете
Ну так вот пусть компилятор доказывает. А "мамай клянус" утверждениям я не верю.
То есть в фразе «производительность и безопасность ортогональны, и не вам не всегда обязательно жертвовать одним в пользу другого» нет ничего про безопасность? Ясно-понятно.сколько раз мне надо тыкать вас в вашу же цитату «Наглядный пример — бесплатные ссылки & вместо вполне себе платных shared_ptr.» прежде чем вы признаете что облажались?
Зиро-кост абстракцию такого вида без поддержки компилятора вы не напишетеда ладно? Вот прям невозможно написать?
Попытка модифицировать значение на которое кто-то держит ссылку — это ошибкаНо это не ошибка.
Можно сделать арены, чтобы ссылок не было, можно убрать мутабельность, вариантов много.я хочу писать простой, понятный и производительный код, а не плясать по прихоти компилятора
Ну так вот пусть компилятор доказываетценой стоимости в рантайме? Тогда ваше утверждение «Безопасность и производительность абсолютно ортогональные вещи» — ЛПП.
сколько раз мне надо тыкать вас в вашу же цитату «Наглядный пример — бесплатные ссылки & вместо вполне себе платных shared_ptr.» прежде чем вы признаете что облажались?
shared_ptr используется вместо сразу трех вещей, которые есть в раст: &, Rc и Arc. А теперь посмотрите, какая относительная частоат их использования в среденй растопрограмме. Понятное дело, что когда вам надо в рантайме считать ссылки то & не подойдет. По почти всегда можно посчитать всё статически.
да ладно? Вот прям невозможно написать?
Я так подумал, что вполне напишете. Ок, это я не то сказал.
Но это не ошибка.
Вот я бы очень расстроился, если бы мне кто-то дал ссылку а потом начал по ней что-то менять.
я хочу писать простой, понятный и производительный код, а не плясать по прихоти компилятора
Ну это тогда вам в питон. А то в С++ злой компилятор тоже не разрешит вместо числа строчку передать.
ценой стоимости в рантайме? Тогда ваше утверждение «Безопасность и производительность абсолютно ортогональные вещи» — ЛПП.
То что компилятор проверил в рантайме не нужно, потому что все уже проверено.
Вот я бы очень расстроился, если бы мне кто-то дал ссылку а потом начал по ней что-то менять.
Вы же шарповик, если не ошибаюсь? А почему вы в C# не расстраиваетесь, когда меняется содержимое каких-то объектов, ссылки на которые у вас есть? Там же практически все объекты только по ссылке и доступны, кроме примитивных типов.
Весьма обижаюсь. Поэтому делаю только гет-онли интерфейсы и достаточно активно юзаю System.Collections.Immutable
.
Да нет, не особо. Но даже фреймворки которые мы используем вроде той же акки очень настоятельно просят так не делать. Вот мы и не делаем.
Ну подумайте сами. Вот вам дали ссылку на итератор. вы с ней че-то там делаете, и тут кто-то взял и эту ссылку инвалидировал. Неужели это нормально?
Ну а в расте это называется interior mutability и точно так же разрешена) А вот внешняя мутабельность — с ограничениями борровчекера. Вроде звучит логично.
Какой же это interior mutability, если этот NetworkStream постоянно дергает за ниточки третья сторона — ОС посредством оперирования состоянием его сокета? :)
Именно такая. Объект мутируется, хотя вам кажется, что нет.
А вообще проблема с тем, что &mut
означает не то, что можно было бы подумать. Тут неплохо объясняется.
Ну как же "мне кажется, что нет", когда я знаю, что он мутируется, на этом, собственно, вся логика работы с ним и построена :) Да, я в курсе, что раст зачем-то очень любит смешивать понятия мутабельности и владения, хотя они по большому счету ортогональны.
Как по мне, например, Rust, раскладывая все по аспектам, наглядно показывает, как много мы «подразумеваем», когда описываем простые вещи. Да, многословно, да, приходится писать с оглядкой на компилятор. Но, может быть, это и нормально, может, так и надо?
Средства ввода-вывода и другие виды взаимодействия с ОС или с железом напрямую — это выход за пределы языка и они всегда чем-то ограничиваются и во что-то заворачиваются.
А вот предоставляет ли язык абстракции, позволяющие их безопасно и правильно использовать или это только в документации прописано…
Какая связь между "безопасным и правильным использованием абстракций" и "попытка модифицировать значение, на которое кто-то держит ссылку — это ошибка" и почему одно из другого должно непременно следовать?
Stream
как много мы «подразумеваем», когда описываем простые вещи
Это вы ещё на коданные, корекурсию и коиндукцию не смотрели.
shared_ptr используется вместо сразу трех вещей, которые есть в раст: &, Rc и Arc. А теперь посмотрите, какая относительная частоат их использования в среденй растопрограмме.а заодно посчитайте частоту использования shared_ptr в с++-программе. И вы наверно удивитесь, но ссылки используются в с++ на несколько порядков чаще, чем shared_ptr.
Вот я бы очень расстроился, если бы мне кто-то дал ссылку а потом начал по ней что-то менять.так я же не отдаю эту ссылку, ни вам ни кому бы то ни было. Она всё внутри той же функции (main), в которой лежит объект, ни в какие потоки она не передается, не инвалидируется и не инвалидирует другие ссылки, и ни в каких инстансах, которые переживут значение, не сохраняется. Эти условия, кстати, мог бы проверить и компилятор. То есть запись по этой ссылке совершенно безопасна и… по какой причине я не могу это сделать?
Ну это тогда вам в питон. А то в С++ злой компилятор тоже не разрешит вместо числа строчку передать.не надо сравнивать типизацию и borrow checker. Нарушение контракта типа в с++ (обращение к инстансу T как к U) как правило является ошибкой, а в подавляющем большинстве исключений определено стандартом с++ (см. правила алиасинга и reinterpret_cast'а). Нарушение контракта borrow checker'а почти никогда не являлось бы ошибкой, если бы вообще было определено в расте.
То что компилятор проверил в рантайме не нужно, потому что все уже проверено.у вас приступ?
не надо сравнивать типизацию и borrow checker. Нарушение контракта типа в с++ (обращение к инстансу T как к U) как правило является ошибкой, а в подавляющем большинстве исключений определено стандартом с++ (см. правила алиасинга и reinterpret_cast'а). Нарушение контракта borrow checker'а почти никогда не являлось бы ошибкой, если бы вообще было определено в расте.
Вот с этим я и не согласен. Кмк это ошибка того же рода, что строки вместо чисел передавать. Не согласны — пожалуйста, пользуйтесь плюсами на здоровье.
Вот с этим я и не согласен. Кмк это ошибка того же рода, что строки вместо чисел передаватьВ этой фразе правильно только «как мне кажется». С точки зрения стандарта с++, такое поведение определено. С точки зрения LLVM AST, в которое компилится раст, такое поведение тоже определено. Т.о. это ограничение существует только в rust, только ради безопасности и обходить его иногда приходится, зачастую ценой накладных расходов в рантайме. А это противоречит вашему утверждению «Безопасность и производительность абсолютно ортогональные вещи».
А можете заодно ответить на вопрос «То есть запись по этой ссылке совершенно безопасна и… по какой причине я не могу это сделать?»?
А можете заодно ответить на вопрос «То есть запись по этой ссылке совершенно безопасна и… по какой причине я не могу это сделать?»?
Была пародия, по-моему от КВНщиков, на известную песню Белого Орла, и она звучала примерно так — «потому что нельзя, потому что нельзя, потому что нельзя, ПОТОМУ ЧТО НЕЛЬЗЯ (громко басом)». Вот примерно поэтому :)
А можете заодно ответить на вопрос «То есть запись по этой ссылке совершенно безопасна и… по какой причине я не могу это сделать?»?
Потому что это могло бы позволить совершить UB в сейф расте, а это нарушило бы его гарантии.
Потому что это могло бы позволить совершить UB в сейф расте, а это нарушило бы его гарантии.во-первых, правила борроучеккера можно было бы ослабить сохранив невозможность UB. Во-вторых, на одну ошибку которую он предотвратит, он 1000 раз только помешает, это замедляет разработку, а в ~трети случаев еще и рантайм. Было бы соотношение хотя бы 1 к 10 — было бы о чем говорить. А то получается что быстрее написать на плюсах, покрыть тестами и отревьюить; это помимо ошибок памяти еще и логические поймает.
Можете.
std::cell::Cell, смотрите функции get() и set() ЕМНИП
не надо сравнивать типизацию и borrow checker
Э, субструктурная типизация же, ну.
Может, вам ещё и выражение предикатов в типах не надо сравнивать с типизацией?
Ну это тогда вам в питон.
Там про производительность было, так что нет.
Ну и про понятность, но то такое, субъективное.
я хочу писать простой, понятный и производительный код, а не плясать по прихоти компилятора
А зря, компилятор иногда умнее и всегда внимательнее, чем я.
Может, вы — сверхчеловек, не знаю.
ценой стоимости в рантайме?
Зачем?
Чем выразительнее система типов, тем больше проверок вы можете перенести в компилтайм.
Написать его аналог на плюсах несложно. Но никто не пишет… наверно потому, что не нужен?
Прочитал вас и вспомнил, как реализовывал что-то похожее минимум в трёх разных проектах, чтобы оверхеда от флага об optionality не было.
Я просил привести пример кода на расте, не выразимого в плюсах без доп. накладных расходов.
Лучше при всех таких спорах учитывать ещё стоимость модификаций и устойчивость к ошибкам при модификации и поддержке кода.
А вообще, ну, любой код, где компилятор обязан иметь в виду алиасинг, не?
Зачем?речь о системе типов недостаточно выразительной, чтобы не запрещать многие корректные и оптимальные действия. Скажем, можно написать язык, оперирующий исключительно арифметическими операциями. В таком языке невозможно совершить ошибку работы с памятью, но почему-то таких языков маловато.
Чем выразительнее система типов, тем больше проверок вы можете перенести в компилтайм.
Лучше при всех таких спорах учитывать ещё стоимость модификаций и устойчивость к ошибкам при модификации и поддержке кода.предположим я всё учитываю, не переживайте за это. Я хочу найти истину в споре.
А вообще, ну, любой код, где компилятор обязан иметь в виду алиасинг, не?а что, хоть в одном компиляторе раста уже включили strict aliasing?
речь о системе типов недостаточно выразительной, чтобы не запрещать многие корректные и оптимальные действия.
Они всегда будут, потому что все тайпчекеры, которыми вам мне (и, вероятно, PsyHaSTe) захочется пользоваться, заведомо консервативны.
Вопрос в том, насколько это серьёзное ограничение на практике.
а что, хоть в одном компиляторе раста уже включили strict aliasing?
Нет, LLVM (написанный на плюсах) ломается.
потому что все тайпчекеры, которыми захочется пользоваться, заведомо консервативны.чекер о котором речь скорее «примитивен» чем «консервативен». И это серьезное ограничение
Вопрос в том, насколько это серьёзное ограничение на практике.
Нет, LLVM (написанный на плюсах) ломается.а вы уверены, что дело только в llvm?
чекер о котором речь скорее «примитивен» чем «консервативен».
Это формальный термин, означающий, что существуют семантически корректные программы, отвергаемые тайпчекером.
а вы уверены, что дело только в llvm?
Нет, конечно, я же не знаю раст.
Это формальный термин, означающий, что существуют семантически корректные программы, отвергаемые тайпчекером.Допустим, есть множества языковых конструкций языка X, опровергаемых тайпчекером Y и некорректных Z. Хотим: X >> Y == Z, имеем X > Y >> Z. Чем меньше X — Y, тем сложнее решать задачу в его пределах, что вводит пенальти на стоимость разработки и рантайм. Тайпчекер перестает быть актуальным если это пенальти выше такового от отсутствия тайпчекера.
При этом даже шаред_птр не очень спасут, потому что вот это будет ошибкой компиляции:«А вот это» было бы корректным (т.к. shared_ptr в отличие от ссылки владеет объектом), если бы вы не посадили такой же UB как и в первом примере
…
А вот это — нет:
А когда у вас есть
struct Foo
{
int a;
int b;
};
Foo foo;
int *ptr = &foo.a;
*(ptr + 1) = 10;
это уже стрельба по ногам или ещё нет? Компиляторы-то генерируют ровно тот код, что ожидается.
Вроде как.
это уже стрельба по ногам или ещё нет?
я так полагаю вы не читали утверждение, которое я пытаюсь опровергнуть и даже процитировал?
1[ptr] = 10;
тоже выстрел в ногу.
Это пока компилятор у вас паддинг там не вставил.
Но это уже не стандарт.
А может ли быть паддинг между элементами массива?
Ответ на этот вопрос потребует формального определения паддинга, а мне лень. Но в рамках одного массива, конечно, арифметика разрешена.
Но вот если вы напишете
struct Foo
{
int x[4];
int y[4];
};
то даже если паддинга не будет, записаь x[5]
с целью обращения во второй элемент y
(что я тоже встречал в продакшен-коде) — UB. Даже запись/чтение в x[4]
UB.
alignas ничего не гарантирует про пэддинг (по крайней мере, про его отсутствие).
Конкретно в коде примера ( тогда как это возможно, обеспечить выравнивание по 2м интам при этом умудриться вставить паддинг? )
Берёте и вставляете паддинг размером в один инт.
Непонятно, конечно, зачем, но компилятор имеет право (хотя почему, понятно — всякие asan/ubsan так могут работать).
А вот это миф. Собственно, раст хороший пример, когда с безопасностью можно еще и выиграть в производительности. Наглядный пример — бесплатные ссылки & вместо вполне себе платных shared_ptr. Безопасность и производительность абсолютно ортогональные вещи. То что в некоторых языках решают пожертововать одним ради другого не означает, что это основной закон мироздания.
Ещё туда же ATS можно вспомнить, который, увы, не получил вообще никакого распространения в широких кругах, но по гарантиям и безопасности может зарулить и раст.
Тоже не скажу за всех, но Segmentation Fault на моей памяти самая частая причина почему падает плюсовое приложение.
Большинство других ошибок можно поймать не падая, но разве они от этого перестают быть ошибками?
Конечно перестают. То, что у вас упало на тестах или во время отладки — это не состояние "упало", это состояние "в разработке". Я говорю про ошибки на продакшене и почти всегда это segmantation fault, memory corruption или memory leak.
От последнего Rust не спасает, но по крайней мере единственный известный мне простой способ memory leak в Rust — это циклические ссылки.
Ну еще остаются дедлоки из распространенных ошибок, Rust тут не помогает особо, разве что Mutex всегда под Guard-ом, что не так уж плохо.
Я имею в виду, что ошибки не ограничиваются падениями, независимо от того, продакшн это или нет. Если в программе произойдёт неожиданное деление на ноль или какой-нибудь ABA, она врядли упадёт, но и работать будет некорректно. Memory corruption и memory leak, между прочим, тоже редко приводят к падениям.
А в комментарии, на который я отвечал, Вы почему-то утождествляете ошибки и падения. В таком ключе SEGFAULT конечно естественным образом оказывается "корнем всех зол".
К тому же, безопасность работы с памятью — это устранение, ну… наверное 95% ошибок при программировании на С++?
только когда ты студент без стажа, пишущий лабораторные на си с классами. Хоть какой-то стаж + современные плюсы и ошибки памяти уже на несколько порядков реже чем логические.
Никаких ADL, SFINAE, аргументов по умолчанию, анонимных неймспейсов, static thread_local volatile переменных, неявных целочисленных и не только кастов, strict aliasing-а, noexcept, rvale, lvalue, xvalue, богомерского std::string, ещё более богомерского std::wstring, 20-ти видов инициализации, include-файлов, и бог знает чего ещё
с остальным могу согласиться (исторические причины, все дела), но что плохого в выделенных кусках-то?
— ADL помогает при написании generic кода, да и без него перегрузка операторов в неймспейсах бы не работала
— SFINAE — более общий и функциональный инструмент чем трейты раста.
— thread_local переменные могут быть полезны
— Неявные касты — чего в них плохого то? По сути, уменьшает бойлерплейт
— В любом языке с exception'ами noexcept в том или ином виде нужен
По итогу: раст и надёжнее, из-за того, что убрали почти все способы отстрелить себе ногу. И проще, потому что не нужно знать Make, CMake и ещё с десяток менее популярных или даже самописных систем сборки.
а система сборки раста умеет во все эти «Make, CMake и ещё ...» чтобы интегрировать его в уже существующие проекты, или интегрировать существующие проекты в него? А то получается, что энтропия-то не уменьшилась
только когда ты студент без стажа, пишущий лабораторные на си с классами. Хоть какой-то стаж + современные плюсы и ошибки памяти уже на несколько порядков реже чем логические.
Видимо всякие openssl и tox'ы пишут всякие студенты на лабораторках.
Tox: Written in C. Initial release <=2011
Самому-то не стыдно приводить старый сишный код как пример современного плюсового с ошибками?
только когда ты студент без стажа, пишущий лабораторные на си с классами.
Или когда ты поддерживаешь систему, написанную давно может даже и студентами
ADL помогает при написании generic кода, да и без него перегрузка операторов в неймспейсах бы не работала
А эта фича действительно нужна? Она и вправду может облегчить иногда написание кода, как жаль, что код приходится в основном читать.
SFINAE — более общий и функциональный инструмент чем трейты раста
… которое опять же порождает write-only код. Вспомните любые пляски с std::enable_if :) Это мощно, это круто, но это почти всегда делает код не очевидным.
thread_local переменные могут быть полезны
Бывают. Вот только семантически они вообще не отличаются от обычных и в большой базе кода вообще хрен разберёшь обычная это переменная или thread_local.
Я уж не говорю про то, что ключевое слово добавляет зависимость от libstd++. Огонь прост)
Неявные касты — чего в них плохого то? По сути, уменьшает бойлерплейт
Ну… Неявные касты между числами ещё можно принять, но ведь есть ещё non-explicit конструкторы, которые порождают просто эпические приколы иногда)
Или когда ты поддерживаешь систему, написанную давно может даже и студентамиблин давайте может тогда каждый день переходить на новый яп, ведь на нем нет написанного легаси?
А эта фича действительно нужна? Она и вправду может облегчить иногда написание кода, как жаль, что код приходится в основном читать.без неё написание некоторого кода невозможно. Например, использование в библиотеке перегрузок функций для пользовательских типов в пользовательских неймспейсах.
… которое опять же порождает write-only код. Вспомните любые пляски с std::enable_if :) Это мощно, это круто, но это почти всегда делает код не очевидным.Я говорю об SFINAE как о методе решения задачи статического полиморфизма. Да, синтаксически он плох, впрочем, это поправят концепты. А может он больше, чем трейты.
Вот только семантически они вообще не отличаются от обычных и в большой базе кода вообще хрен разберёшь обычная это переменная или thread_local.go to definition?
Ну… Неявные касты между числами ещё можно принять, но ведь есть ещё non-explicit конструкторы, которые порождают просто эпические приколы иногда)И есть еще операторы явного/неявного каста. Это может быть и порождает неочевидный код, но как минимум синтаксически удобнее чем писать as VeryLong::TypeLiterallyNooneCares::About.
без неё написание некоторого кода невозможно. Например, использование в библиотеке перегрузок функций для пользовательских типов в пользовательских неймспейсах.Мне кажется, что как только возникла потребность в генерации сложных алгоритмов на шаблонах (а это лет эдак 15 назад) нужно было придумывать полноценный метаязык кодогенерации (императивный или декларативный, а лучше оба) с нормальными читаемыми сообщениями об ошибках, а не развивать кучку «грязных хаков» которые в итоге получились, с сообщениями об ошибках, которые фиг прочитаешь.
И кодом, для чтения которого нужно лет эдак 5 опыта.
блин давайте может тогда каждый день переходить на новый яп, ведь на нем нет написанного легаси?
Если "каждый день" — это раз в… эээ… ну лет 30 плюсы существуют? Даже если взять начало от релиза С++98, всё равно выходит прилично. Как по мне наоборот выглядит немного наивно продолжать писать на языке с учётом всех его недочётов, появившихся из-за проблем железа эпохи динозавров.
без неё написание некоторого кода невозможно
Точно ли это правильный путь к написанию действительно понятного кода? Всё "невозможные" случаи, что приходят в голову, мягко говоря, пахнут.
Я говорю об SFINAE как о методе решения задачи статического полиморфизма.
А я говорю о SFINAE, который как только не используют :) Я бы рад сказать всем "хватит", но ведь не послушают :) Опять же в том же Rust та же самая проблема решается совершенно понятнейшим образом, не требующим SFINAE.
go to definition
Допустим я написал код в расчёте на thread_local, а потом кто-то по каким-то неведомым причинам удалил thread_local) Компилятор даже warning-а не кинет.
Это может быть и порождает неочевидный код
Этой причины уже достаточно, чтобы отказаться от этой фичи, но
удобнее чем писать as VeryLong::TypeLiterallyNooneCares::About
В целом неважно как писать. Я топлю только за явный каст всего чего только можно, а как это синтаксически мне не интересно особо. Я просто хочу всегда видеть такие касты.
Если «каждый день» — это раз в… эээ… ну лет 30 плюсы существуют? Даже если взять начало от релиза С++98, всё равно выходит прилично. Как по мне наоборот выглядит немного наивно продолжать писать на языке с учётом всех его недочётов, появившихся из-за проблем железа эпохи динозавров.вы же понимаете что кол-во легаси кода на ЯП пропорционально его возрасту и популярности? И что оно же зачастую является причиной остаться на этом яп?
Если говорить о долгоидущих планах, в с++ можно завезти эпохи и выводить из обихода устаревшие его особенности. Да, процесс займет лет 15. Не думаю что долю с++ можно откушать раньше.
Точно ли это правильный путь к написанию действительно понятного кода? Всё «невозможные» случаи, что приходят в голову, мягко говоря, пахнут.на ADL строятся механизмы расширения стдлибы. Они «пахнут»? У вас есть предложения лучше?
Опять же в том же Rust та же самая проблема решается совершенно понятнейшим образом, не требующим SFINAE.в раст она решена не полностью. Например, в расте тип не может удовлетворять трейту, о существовании которого он не знает.
Допустим я написал код в расчёте на thread_local, а потом кто-то по каким-то неведомым причинам удалил thread_local) Компилятор даже warning-а не кинетто есть ваш аргумент "Вася поменял 1 на 2 и всё сломалось, плохой компилятор"? Убирание thread_local не вводит UB если вы не возвращаете на объект ссылку (тут компилятор ругнется), а в остальном это логическая ошибка.
Я топлю только за явный каст всего чего только можно, а как это синтаксически мне не интересно особо. Я просто хочу всегда видеть такие касты.но это ужасно непрактично. Увеличение объема кода из-за ненужных явных кастов уменьшает читаемость сильнее, чем явность типизации увеличивает.
в с++ можно завезти эпохи и выводить из обихода устаревшие его особенности.
Или просто начать отказываться от проектов на С++ в сторону проектов на Rust? Ну понятное дело не всем и не сразу, но лет за 15 можно и смоч.
У вас есть предложения лучше?
impl Trait for Type? И не городить невероятной магии.
расте тип не может удовлетворять трейту, о существовании которого он не знает.
И слава богу! Господи, да в целом это единственная вещь в плюсовых шаблонах, которая раздражает больше всего. В этом плане в С++20 хоть концепты завезут и на том спасибо.
то есть ваш аргумент "Вася поменял 1 на 2 и всё сломалось, плохой компилятор"?
Да, это мой аргумент. Но виноват правда не компилятор, а правила языка. Если язык мне позволяет такое чудить, то это плохо. TheadLocal переменные не должны выглядеть как обычные. Ровно как и atomic. Потому что иначе без go to definition вообще не разобраться что происходит в конкретном кусочке кода.
Увеличение объема кода из-за ненужных явных кастов уменьшает читаемость сильнее, чем явность типизации увеличивает.
У меня другой опыт :) Отчасти вы конечно правы, но проблема скорее в том, что в С++ для каста слишком громоздкие ключевые слова (один reinterpret_cast чего стоит).
Или просто начать отказываться от проектов на С++ в сторону проектов на Rust? Ну понятное дело не всем и не сразу, но лет за 15 можно и смоч.для многих проектов на это понадобится куда больше 15 лет. Так а смысл тогда?
Господи, да в целом это единственная вещь в плюсовых шаблонах, которая раздражает больше всеговаше мнение здесь иррелеватно. Плюсовые концепты де-факто могут больше чем трейты раста. И я могу привести пример когда это может быть нужно.
TheadLocal переменные не должны выглядеть как обычные. Ровно как и atomic.потому что… лично вы так думаете?
У меня другой опыт :) Отчасти вы конечно правы, но проблема скорее в том, что в С++ для каста слишком громоздкие ключевые слова (один reinterpret_cast чего стоит).часто можно написать To(from), и это будет явный вызов explicit конструктора/оператора преобразования. Но проблема-то в другом, в том, что иногда неявные касты нужны для реализации удобного и читаемого API. Можно много говорить о том, как ужасен std::vector, но без неявного каста v[i] <-> bool пользоваться им было бы попросту больно
Или просто начать отказываться от проектов на С++ в сторону проектов на Rust? Ну понятное дело не всем и не сразу, но лет за 15 можно и смоч.
А на хрена? Лично я, например, считаю, что Rust не поможет мне решить ни одной из действительно насущных проблем, с которыми я сталкиваюсь в реальных задачах, а вот добавить мне лишнего геморроя в плане борьбы со своим убогим и негибким borrow checker и необходимости писать лишние обертки для того, что я сейчас могу просто вызвать напрямую — это он запросто. Когда я действительно увижу в нем что-то, что может реально облегчить решение моих задач — тогда его можно будет рассмотреть. Пока — нет.
— Неявные касты — чего в них плохого то? По сути, уменьшает бойлерплейт
Или увеличивает, потому что когда нужно гарантированно не присвоить int64_t к uint32_t, то кажется придется городить шаблонный класс с кучей операторов.
Ну и даже создатели языка считают, что приведения типов — зло, но нужное и лучше его делать явным.
Или увеличивает, потому что когда нужно гарантированно не присвоить int64_t к uint32_t, то кажется придется городить шаблонный класс с кучей операторов.или -Wimplicit-int-conversion -Werror. Часть кастов всегда являются корректными и вполне могут быть неявными, например каст uint32_t в int64_t, float в double или Derived* в Base*.
Ну и даже создатели языка считают, что приведения типов — зло, но нужное и лучше его делать явным.Пользователь же имеет контроль над user-defined conversions, может его по желанию не определять или помечать явным.
могут приводить к тому, что на разных платформах программа (например, генератор рельефа в игрушке) приводит к разным результатам. Так что и тут — спорно.
Поделитесь, пожалуста, ссылкой, где почитать про опции вроде -Wimplicit-int-conversion -Werror
Derived* в Base* — это наверное единственный «хороший» неявный каст, и то можно придумать, что это вообще не каст, так как Derived is a Base.
Пользователь же имеет контроль над user-defined conversions, может его по желанию не определять или помечать явным.
Да, и это хорошо, только лично я бы предпочел, чтобы по умолчанию нельзя было делать что-то неявно, но была возможность это включить. То есть в место ключевого слова explicit, наоборот писать implict где нужно.
Зачем избегать друзей, или как я растерял все свои плюсы