Комментарии 294
Автору, мне кажется, сильно повезло работать с каким-то небольшим подмножеством C++ на своих проектах. В крупных проектах ситуация отличается.
Всё упирается в то, можно ли написать большой полезный проект большой группой людей с полным соблюдением стандарта C++. Я утверждаю, что это невозможно, особенно если есть код старше 5-10-20 лет, см. ниже.
Да, это хорошо и большое количество С++ компиляторов обусловлено временем появления С и С++, это примерно 40-50 лет назад
А чем конкретно хорошо большое количество компиляторов? Я не увидел аргументов дальше в статье. Поддержка большого количества платформ (в том числе старых) — это, безусловно, преимущество, но само по себе разнообразие ничем не хорошо.
Кроме компиляторов С++ есть такое понятие как стандарт
У меня про это целый доклад был: "Санитайзеры и стандарт не спасут":
Компиляторы постоянно нарушают стандарт: разрешают некорректный код, компилируют корректный код неправильно, не поддерживают фичи стандарта, или поддерживают их неправильно. Не говоря уже об расширениях компилятора, которыми можно случайно воспользоваться и не заметить.
Пользователи постоянно нарушают стандарт, обычно случайно. Например, до 2007 года не было никаких проблем с обнаружением переполнения конструкцией
a > a + 100
, но внезапно появились. Стандарт не менялся. Просто компиляторы начали чуть больше оптимизировать.Стандарт довольно сильно ограничен. В C++17 и раньше технически невозможно написать
std::vector
с соблюдением стандарта. По факту все, конечно, пишут, и проблем не возникает. Пока. Посмотрим, как будут вести себя компиляторы через двадцать лет на сегодняшнем коде.А вещественные числа вообще компилятор может трактовать почти как хочет. И в статье, кстати, там всё ещё нет никаких гарантий по стандарту, просто на конкретном компиляторе перестало воспроизводиться.
Недавний пример с работы: одна и та же программа, скомпилированная одним и тем же GCC на одной машине в 32-битном и 64-битном режиме выдавала разные результаты вычислений. При этом всё полностью по стандарту. Просто в одном случае excess precision компилятор добавил, а в другом не добавил.
Есть огромное количество совершенно неожиданного UB: https://github.com/Nekrolm/ubbook
И знаете как мне это удалось? Я просто использовал конкретный стандарт языка, в первом случае С++ 98, во втором скажем так общий стандарт С++ до шаблонов. Ну не прелесть ли? Что вы сейчас думаете о совместимости С++?
Совместимость C++ двадцатипятилетней давности, как вы продемонстрировали, прекрасная. А если на C89 писать, то будет ещё лучше. Но обычно хочется писать хотя бы на C++11, где с совместимостью всё уже далеко не так хорошо.
Кто что подразумевает под словами "C++" — отдельный вопрос.
С++ никогда не ломает обратную совместимость
C++ ломает обратную совместимость в каждом, каждом выпущенном стандарте. Даже есть отдельный раздел "что сломали на этот раз". А если вам примеры оттуда кажутся академическими — так это потому что они минимизированы до предела. В реальном коде каждый такой пример был бы размазан между трёмя файлами и парой сотен строк.
вы просто берете и собираете код компилятором с поддержкой нового стандарта
И у вас старый код перестаёт компилироваться в лучшем случае, а в худшем — просто меняет своё поведение. Я и на работе это иногда вижу, и был про отличный доклад: "Как обновить компилятор и не тронуться". Бонусные баллы, если у вас код не писался исходно с расчётом на совместимость между компиляторами.
У нас не компилится проект, по причине того, что компилятор считает программиста, обезьяной с клавиатурой в руках. Прям как в Rust'е.
Повысить уровень предупреждений можно, но до Rust ещё очень, очень, очень далеко. Снова упрощённый пример с работы:
#include <iostream>
#include <string>
std::string read() { return "foo"; }
int main() {
std::string_view s = read();
std::cout << s << "\n";
}
Компилируется без предупреждений, запускается, выводит foo
. А UB есть. И даже выстрелит, если строчку сделать подлиннее. А если не делать — пройдёт любое тестирование. Конечно, опытный программист на C++ сразу заметит подвох, но мы же статический анализ обсуждаем. Address Sanitizer тоже может помочь, но только если весь код будет скомпилирован сразу с ним, а не размазан между зависимостями, которые задолбаться перекомпилировать.
Кстати, если слишком сильно повысить уровень предупреждений — пойдут false positives, я лично видел это массово у студентов в домашних заданиях, рассказывал, как заглушить, и находил соответствующий баг компилятора.
Не нужно использовать сырые указатели, делать new в каждой сточке, увлекаться арифметикой указателей. А использовать высокоуровневые концепции и надстройки. Это и умные указатели, контейнеры, шаблоны и т.д. Это уже давно есть и работает, нужно просто не делать себе больно и всё.
Это всё не помогает избежать UB. Есть ODR, есть порядок инициализации, есть желание некоторых программистов нарушить strict aliasing "потому что так же эффективнее", есть неявное взятие чистых ссылок и указателей (пример кода выше со string_view
)... И сошлюсь ещё раз на UBBook с бытовыми примерами: https://github.com/Nekrolm/ubbook/
Вам всё равно нужно обрабатывать ошибки.
Разумеется, тут соглашусь. Rust не решает всех проблем. Как и статическая типизация не решает всех проблем, но многие (не все) почему-то предпочитают писать крупные длинные проекты на языках, где опечатка в имени переменной выявляется заранее, а не во время тестирования.
Пишу на расте и на плюсах. Почитал ваш батл - чуму на оба ваших дома: какой-то антитехнический евангелизм.
Ни один из вас не описал нормально ни сильные стороны языка, за который он топит, ни слабые стороны языка против которого он топин.
Я тоже не описал, и честно говоря, мне лень таким заниматься - поэтому я не пишу статью на эту тему )
Я постарался в кратце, так сказать широкими мазками. Как вышло:)
Надо к утверждением хоть примеры кода прилепить, чтоб понимать в чем визуальная разница и сравнить результаты компиляции. А так получилась сплошная вкусовщина и набросы.
Я и не пытался написать фундаментальный труд и сравнить С++ vs Rust. Обозначил общими моментами с которыми я не согласен. Без негатива и оскорблений.
Почему в С++ это именно так, а не иначе.
Какая цель-то? Как дети малые
Один написал, какой у него крутой руст. И что, все сразу должны все бросить и туда ломануться? Второй накатал ответную простыню, мол, ребята, бегите обратно)
Что вы пишите на расте? Это часть работы или хобби?
Микросервисы. Работа.
Микросервисы
Не читайте мою статью на которую я ссылаюсь:)
Кстати, вот тут недавно делал наброс по поводу токсичности растового комьюнити: https://habr.com/ru/articles/804915/comments/#comment_26680375
Если обвинить любое комьюнити в токсичности, то дальше оно вот на все сто процентов само докажет правдивость данного суждения.
Ну попробуйте пойти в комьюнити психологов, священников или педагогов и обвинить его в токсичности. Первая рекация будет явно не "ах ты, нахал, что ты себе позволяешь, сам дурак!"
Технари вправду более токсичны.
Первые тебе сразу поставят диагноз (что является более токсичным т.к. переход на личности)
Вторые тебя вообще могут посадить, если ты неуважительно обвинишь в токсичности
Про третьих тоже довольного много историй, особенно с универов, где они цари, боги и могут делать все что вздумается.
Я не знаю как вы так подобрали список, но технари с их "ваш код говно" смотрятся как слепые щенки среди этих групп. Вы могли взять коммьюнити попроще, ну типа не знаю, любителей вязать или там, нумизматов?
Первых отучают ставить диагноз ещё на втором курсе, и потом это считается дурным тоном внутри комьюнити. Комьюнити священников это самое мягкое общество, которое я когда-либо видел. А педагоги имеют ангельское терпение.
Не забывайте, мы с вами говорим об общении внутри комьюнити. И если олдин священник принесёт своим друзьям его мысли по поводу трактовки тех или иных притчЮ вряди ему будут говорить, что он лох и никто так не трактует!
Это сферические психологи, священники и преподаватели в вакууме. Реальность более сурова.
В мягком сообществе священников, вон, до извержений из сана доходит. Никогда не слышал, чтобы кого-либо извергли из программистов. Хотя идея-то, может, и неплохая была бы.
Видел ваш код, будучи ещё зеленейшим джуном. не впечатлило. Меньше делайте набросы )
Врёте небось. В какой компании / проекте?
Если память не изменяет, а она может изменять, то мпс. Помните такую аббревиатуру? )
МаксиПроСофт? Моё первое рабочее место где я вообще что-то делал на с++ (а до этого только на си писал лабы/курсовые) и, как вы говорите, был зелёным джуном. 2009 год. Проект был на MFC с тоннами легаси и архитектурными проблемами уже тогда и от меня там осталось только пару тривиальных багфиксов. Скажем так, с тех пор я прилично вырос в скиллах.
Видите, всё помните. Отмазки про легаси и архитектурные проблемы в случае с тривиальными, как вы выразились, багфиксами, не работают. Тем более, что в 2009 году всё уже собиралось под 2008 студией, что говорит о том, что код был написан неплохо. Тем более, если багфиксы были тривиальные, можно было бы сделать и получше. Так что меньше делайте набросы, растите. К тому же, если помните атмосферу мпс (и отдельных личностей там), то должны и помнить, как в меру токсичное комьюнити способствует скорейшему профессиональному росту )
Смешно, вы меня упрекаете в том что я на первом месте работы писал якобы плохой код аж в течение 2 месяцев (сколько там того кода было?). При том что это была моя первая работа на плюсах а мфц я до этого в глаза не видел. На компе с треснутой материнкой и регулярными бсодами, потому что джуну не могли сразу нормальный комп дать. А когда дали - то долго не могли успокоить техлида, которого бомбило от того что джуну дали новый более мощный комп.
Два месяца в начале моей карьеры как-то странно сравнивать с моим нынешним 15-летним стажем, не находите?
P.S. к сожалению, кроме директора и его жены я там никого не вспоминаю с ходу, давно это было. Так что можете представиться, если хотите более предметно поговорить.
Кто вас упрекает? Я просто оценил, чего вас так сразу бомбит? Про техлида - брехня. Просто потому, что там не было таких званий. Зато гуляла байка о том, что кто-то очень просил лычки архитектора. ))
Что касается оборудования, то мне спустя некоторое время выдали и более мощный комп, чем у старожилов, и даже монитор лучше... Так что брехня вдвойне.
Два месяца в начале моей карьеры как-то странно сравнивать с моим нынешним 15-летним стажем, не находите?
Странно - это когда вы набрасываете, махая своим стажем (ну как будто вам уже можно). За 15 лет - только плюсы и несчастный Qt? Негусто (это если хотите оценку). Так что поменьше набросов )
Позволю тут влезть.
Если вкратце, чувак написал на расте проект, но Rust сообщество набросилось на него как стая гиен, с утверждениями, что у автора код говно, проект говно и сам он говно. А причина в том, что он якобы не использует некий стиль растовиков, переменные не так называются, методы не в том case стиле и вообще так не пишут на Rust. Итог очевиден, автор со словами я ипал я увольняюсь, свалил с данного языка, ушёл в гугл и пишет код на С++ попивая своё винишко с Мальдив:)
Если это про историю с автором Actix-web, то это ложь от первой до последней буквы. Во первых автор нарушал важный принцип написания кода в библиотеках, которые выкладывал на crates.io. Проблема там в том что от safe кода предполагается, что в нем нет таких инвариантов, нарушая которые он вдруг перестает быть безопасным, в этом случае методы надо помечать как unsafe. Ему на это указали и даже прислали MR, который фиксил проблему, но он его закрыл не помню с какими словами, но если мне память не изменяет, то со словами, что ему это не интересно. Ну то есть люди потратили время, чтобы исправить этот косяк, а он их послал в грубой форме, естественно, что они не стерпели такого.
К тому же он потом все равно вернулся и начал себе тихонько пилить ntex. Других таких громких историй не помню.
На Полухина ссылаться это такое - он известный хейтер Раста и обожает передергивать. Ну и блин, все подобные статьи, что в защиту раста, что в его хейт - это передергивание. Пока могу сказать, что оба языка активно развиваются и работы на всех хватит, но мне лично плюсы кажутся уже слишком сложными и запутанными.
Мре как то нужна была небольшая библиотека на Rust. Нашел пару. Одна не очень подошла. Другая функционал нужный имеет, но! В каждой строке но clone()
Не поленился, сделал MR-запрос чтобы переделать код в нормальную форму. Отправил запрос автору. А в ответ - я во владении переменнвми не разбираюсь и твой фикс даже не буду пробовать читать. Отказался брать.
Ну чтож, хозяин - барин. Но помню был несколько впечатлен :)
Если это про историю с автором Actix-web
Да, это оно. Я рад, за автора. Я не могу представить такую ситуацию не только вС++ сообществе, но и в сообществе любого другого языка.
Сама ситуация настолько дикая...
Ой да ладно вам. Вон, в сообшестве JavaScript вон вообще в NPM-пакетах иногда лютая дичь творится и "пол-интернета" убивало. У каждого крупного проекта вне зависимости от языка есть свои скелеты в шкафу.
Если это про историю с автором Actix-web, то это ложь от первой до последней буквы.
Я понял. Я выделю текст разным цветом и добавлю, что вот этот цвет сарказм, это шутка и т. д :)
люди
идите, что ли, код по-пишите)
и не ставьте, пожалуйста, в следующий раз тег C++/Rust. Не вижу здесь отношения к ним. Лучше напишите человеку в лс и разберитесь кому на вкус что больше нравится. А такие ответки пустая трата времени
Borland C++ 6.0 и выше (уверен, что соберется и на других версиях, я просто не проверял)
Не было у Борланда такой версии компилятора. Закончили они на 5.5 и он у меня есть до сих пор, как и первые их версии.
Видимо, вы путаете с MS VisualC++ 6.0? Такой как раз был после 98го.
Если возвращаться к языку С++, он оброс новыми стандартами, хорошими практиками кодирования, которые позволяют избежать UB и других плохих и неочевидных вещей. Не нужно использовать сырые указатели, делать new в каждой сточке, увлекаться арифметикой указателей. А использовать высокоуровневые концепции и надстройки
И хотя список явно не полный, далеко не только сырые указатели зло, по сути всё правильно -- надо использовать то что надо использовать и не использовать то что не надо использовать. А теперь спустимся на землю. Язык позволяет и не бьет по рукам. В большом коллективе за всеми не уследишь. Вы говорите о новых коцепциях, а я вам - об отсутствии возможности выпилить старые и опасные так, что бы весь коллектив не мог их использовать.
Ликрика -- вот недавно один гуру, говорит что в коре все номера строк правильные и им можно верить. Начинаю объяснять что это не так, что стековые фреймы могут отсутствовать из-за оптимизаций и что тела функций могут быть развернуты компилятором, как и циклы - оптом сразу вектор, и человек не понимает вообще о чем речь. У него в голове про корректность debug info зацклилось. А падало в кору из-за UB - выбег за границу буффера, размещенного в стеке. C++ как и С плевать на такие вещи. Кора показывает на совсем не относящееся к делу место. ASAN четко поймал причину и ASAN мой друг уже очень много лет - его добавили с CLang мир уже лет 15 как?, и перенесли в gcc/g++. А в Rust ASAN - это только nightly build со всеми ужасами ночных сборок, в которых может быть совешенно всё что угодно. Мне пришлось довольно сильно извратиться, что бы собирать 1.77 в prod и тестов и бебага и nightly для тестов с ASAN.
В остальном - у меня тоже большой опыт разработки на C/С++ - с начала 90х на QuickC и BorlandC 3.0 и весь опыт и багаж в нем. Но я вижу большие плюсы Rust и переползаю по мере сил и возможностей. Упехов и вам в осознании вашей правоты или заблуждений, как повезет.
Не было у Борланда такой версии компилятора. Закончили они на 5.5 и он у меня есть до сих пор, как и первые их версии.
Не вполне понимаю, что вы имеете ввиду, но такой продукт, как Borland C++ Builder 6.0 существовал, и я даже им пользовался.
Более того, выходили новые версии. Если верить вики, последняя (11) вышла в 2021. При этом они несколько раз меняли нумерацию версий, были и всякие 2006 - 2010, и всякие XE. Теперь у них опять 11 (и v28 в скобочках).
PS. А, понятно, вы о том, что без дополнения к нащванию Builder версии после 5.5 не было, но фокус в том, что это дополнение к названию часто опускают.
Может быть имелось ввиду, что сам Borland закончился на 5.5, а потом его купили? Или то, что версия bcc32.exe и у Embarcadero долго стояла на 5.5?
Сейчас Билдер перешёл на Шланг (bcc32x.exe/bcc32c.exe), но там есть и старый компилятор bcc.exe, который 7.50:
C:\Program Files (x86)\Embarcadero\Studio\21.0\bin>bcc32.exe --version Embarcadero C++ 7.50 for Win32 Copyright (c) 1993-2021 Embarcadero Technologies, Inc. Revision 7.50.7690.37590
Borland С++ как продукт и как компилятор со всем обвесом закончился версией 5.5. Borland C++Builder - это другой продукт, RAD, он закончился на версии 4.0 после котороый Борланд подася во все тяжкие, уперся в .NET, назвался Inprise, консалтинг какой-то, базы данных, всё это не пошло, компиляторы продали в CodeGear, и потом Embarcadero как чаcть Idera. Я видел что там есть, у меня была версия в приимерно в 2011-12, но сейчас покупать это только ради компилятора особого смысла не вижу. Если вы утверждаете что там после Inprise снова вышел Borland C++Builder - я немного удивлен, потому что не заметил этого в то время, или уже забыл.
Так или иначе, я просто не понял что речь о втором продукте, а не о первом. Потому что второй - ну это больше о С++ версии Delphi. Например, если мы сравним Borland C++ 5.0 и Inprise C++ Builder 5.0 - ну это мягкое с теплым. Разные продукты. Второй как-то не зацепил, а на Borland C++ попахал, было дело, поэтому и подумал что речь о нем.
Не было у Борланда такой версии компилятора. Закончили они на 5.5 и он у меня есть до сих пор, как и первые их версии.
Ну как же не было, если вот он, родненький.
![](https://habrastorage.org/getpro/habr/upload_files/317/ec6/cd9/317ec6cd9dc400a998e302dd3e67268d.png)
До сих пор пишу на MS VisualC++ 6.0 - ловлю кайф.
Продукты Borland - тоже нравятся.
Не понимаю почему так сильно пиарят Rust.
Если он так хорош - то почему такое токсичное комьюнити.
Если же он не достаточно хорош - то зачем его пиарить?
Ох да, особенно весело было им компилировать код с других платформ
for (int i = 0; i < 10; i++) some(i);
for (int i = 0; i < 10; i++) another(i);
писал, что duplicate declaration of variable 'i'.
то же - на VC98, и только в VC2001 кажется ввели ключ компилятора для соответствия стандарту. А уж когда они стали следовать стандарту по умолчанию, и не вспомню.
то есть, на один "хороший" SDL найдётся пара анти-примеров, о которых молчит автор.
А если спросить "зачем брать мерзкий Microsoft, когда был божественный Borland?"
Там новейший Windows SDK, всякий DirectX SDK выходили изначально под VC, а Borland портировал их к себе с задержкой, богомерзские ms-specific всякие __declspec(property) и т.п. заменяя на свои аналоги.
Да не, просто забыл. Да я с этим столкнулся. Просто использую I, j, k.
Всё ради совместимости со старыми компиляторами.
Я сначала разрабатывал под windows, потом под Linux с максимальным уровнем предупреждений + pedantic. Исправлял код. Потом уже портировали под Ms-dos и опять правил код.
Я постоянно собираю под разные компиляторы и системы, что бы выявить проблемы. Пока получается. И ещё на всех системах запускаю обязательно тесты. Они должны полностью проходить под портируемую ОС.
Детский сад, трусики на лямках. Не знать вот этой вот тонкости языковой, это сильно, да. Поколение бумеров, блин.
Может припомнить те славные времена, когда арги функции прописывались вне строки? Оу, нет, это ж С будет. Но, и на плюсах такая ересь тоже бывала.
А уж какая ересь на С для ZX была, ммм... Рекомендую, если с мазохизмом таки на ты ;)
Скорее всего это просто небольшая путаница. Насколько я помню, и фактчекинг в вики меня убеждает в моей мысли, было два отдельных продукта: Borland C++ и С++ Builder который часто назывался Borland C++ Builder. И первый действительно закончился на версии 5.5, а у второго была версия 6.0
Я беру всё компиляторы с данного сайта.
Я взял именно Borland C++ Builder 6.0, о чем и написал.
Ещё там есть просто Borland C++, я взял версию 2.0 под Ms-dos.
Если не ошибаюсь, там ТУРБО С++. И к слову, емнис, лучше было третью версию брать. По совместимости одно и тоже, а баги подчистили и чутка улучшили.
C++ Builder - это фактически Delphi в обёртке C++. Это совсем другой язык. Популярность не получил. Ничего большего по сравнению с Delphi он не предлагал. В теории мог поспособствовать обучению C++, но по факту этого не происходило. Ведь фактически писал на ObjectPascal, только с синтаксисом C.
А вот сам ObjectPascal никуда не делся и продолжает активно развиваться в лице FreePascal. Не сильно слабее C++.
Как говорил один античный персонаж "Oderint, dum metuant", что в переводе означает "Пусть ненавидят, лишь бы боялись".
Что вы набросились друг на друга? На С/С++ написано столько, что любая попытка их заместить здесь и сейчас обречена. Все-таки 50 лет активного использования что-то да значат.
С другой стороны, у Rust отличные перспективы (imho). Да и язык, действительно, интересный. Пусть пытается. Может, что и начнет получаться. Все жанры хороши, кроме скучных. Не ссорьтесь, не создавайте очередную holy war.
Всем добра и побольше интересных задач
Кмк, основное возражение против Rust — это то, что сейчас в универсальных машинах памяти очень много, сборка мусора дешева, поэтому круг задач, где нужно хорошо контролировать память очень мал.
То есть, очень узок круг реального применения Rust, где его замена на какой-нибудь современный компилируемый язык с относительно приличной системой типов Kotlin/OCaml/Haskell/C#/F#/Java/Scala не выдаст примерно ту же производительность. При этом разработка с GC много проще. Хотя и приятных уму загадок меньше.
При этом разработка с GC много проще. Хотя и приятных уму загадок меньше.
Помнится, сишарписты знакомые наваяли что-то с большим количеством создаваемых и уничтожаемых объектов. Результатом для них стало две неприятных загадки. Оно памяти жрало неожиданно много, а сборщик мусора ставил приложение колом на заметное время.
Уж не знаю, фича ли это для С#, или они что-то перемудрили, но попотеть ради получения приемлемой (даже не хорошей) производительности им пришлось преизрядно.
Ради нездорового любопытства, как с этим в С# сейчас?
Достаточно хорошо сейчас. С каждой новой версией языка и самой net среды, что то да подкручиают. Со сборщиком всегда нужно быть на чеку. Но в целом норм.
В ядре постоянный улучшайзинг идёт на эту тему (в том плане, что корные либы используют Span/Memory/Buffer/etc. которые не серят в кучу). Можно брать пример и оптимизировать своё (но нужно это, сами понимаете, редко).
На любом языке можно написать медленно. В шарпах можно словить тормоза или из-за рефлексии или из-за memory traffic. И то и другое - известные проблемы с известными решениями.
Проблема GC не в производительности а в непредсказуемости. Если в большинстве случаев всем пофиг что обработается 99.9% запросов за 70мс а 0.01% за 100мс то есть сферы где предпочтительней что бы все запросы обрабатывались за 80мс но зато без разброса. Или говоря по простому важен реалтайм. Навскидку это эмбедед, геймдев, биржевые боты HFT, критичная инфраструктура. Места где безраздельно властвует/властвовал c++ и c.
А Ocaml/Haskell и прочие скалы никогда не станут мейнстримом просто потому что это слишком сложно для среднего программиста.
Геймдев. Фриз посреди загрузки сцены, это одно, фриз в момент реакции пользователя - смертелен. По счастию, в игровом смысле. После чего, с шарпеем не дружу и у меня на него идиосинкразия.
Так надо и с Windows, и с Linux уходить. У них 100500 фоновых процессов, норовящих проснуться посреди игрового кадра. И привет, смерть в игровом смысле.
Планировщики не такие тупые. Виндовый, например, низкопроритетные потоки может на секунды не ставить на исполнение, и, по возможности, не смешивает высокоприоритетные и низкоприоритетные на одном реальном ядре (а если очень надо, гиперпоточности сгладит эффекты). Так что в реальности обычный бюджетный 6/12 процессор под современной ос без проблем должен переваривать все овер9000 потоков, которые существуют в системе.
Проблема GC не в производительности а в непредсказуемости.
Да, но учтите, что рост быстродействия эту непредсказуемость делает несмертельной для большего и большего числа применений.
Самый тупой вариант — для GUI если средняя задержка отклика на событие занимает 10мс, а изредка есть пик до 200мс, то вы этот пик не заметите (тогда как на компьютере, работающем в 10 раз медленнее числа будут 100мс и 2 секунды — и пики очевидны).
Менее тупой — производительность и память, как правило, можно разменять на предсказуемость. Например, можно использовать более медленный GC для интерактивных программ. Или, если у вас много памяти, можно мусор в пределе вообще не собирать, ну или собирать только тогда, когда программа и так «заблокирована» каким-нибудь длинным вычислением.
То есть, да, ниша для языков без сборщика мусора остаётся, но она сужается по мере роста производительности и развития разных GC.
-------------------------------
Ой, забыл упомянуть, что в non-managed средах есть вопрос композиционности. Вот это ещё одна ниша для языков без сборщика мусора. То есть, вы можете в любой комбинации собирать программу из процедур на FORTRAN, Rust, C++, Pascal и т.д. Но вот поженить Go и Haskell — это уже конкретная задача, не уверен, что решённая на данный момент.
Да, но учтите, что рост быстродействия эту непредсказуемость делает несмертельной для большего и большего числа применений.
Самый тупой вариант — для GUI если средняя задержка отклика на событие занимает 10мс, а изредка есть пик до 200мс, то вы этот пик не заметите (тогда как на компьютере, работающем в 10 раз медленнее числа будут 100мс и 2 секунды — и пики очевидны).
В комментариях есть ссылка на gui работающим на процессоре с десятком mhz.
Если гуй тормозит на современном ПК, я не знаю, тогда на, что там такты ЦПУ уходят.
Если гуй тормозит на современном ПК, я не знаю, тогда на, что там такты ЦПУ уходят.
Очень сжатый ответ — на то, чтобы использовать менее квалифицированных программистов, писать быстрее.
В комментариях есть ссылка на gui работающим на процессоре с десятком mhz.
Я в школе делал GUI, работающий на XT, это 4.7 Mhz. Рядом вещей приходится жертвовать (например перетаскиваемые окна), но для отрисовки прямоугольной кнопки с текстом достаточно.
Поэтому, кстати, полностью российский 300Mhz Эльбрус вполне себе имеет ниши. Более того, на нём прекрасно пойдёт Хайку, только портировать надо.
Я в школе делал GUI, работающий на XT, это 4.7 Mhz. Рядом вещей приходится жертвовать (например перетаскиваемые окна), но для отрисовки прямоугольной кнопки с текстом достаточно.
Поэтому, кстати, полностью российский 300Mhz Эльбрус вполне себе имеет ниши. Более того, на нём прекрасно пойдёт Хайку, только портировать надо.
Я сам любитель всякого старого железа. Про Эльбрус смотрел обзоры Дмитрия Бачило. Современные Эльбрусы не уступают современным железкам.
Сейчас и жертвовать ничего не нужно. Есть аппаратное ускорение, че оно на современном железе то всё тормозит.
Современные Эльбрусы не уступают современным железкам.
Это которые на TSMC сделанные, по 28нм процессу. А в РФ, увы, процесс 90нм, и частота получается всего 300МГц.
Это которые на TSMC сделанные, по 28нм процессу. А в РФ, увы, процесс 90нм, и частота получается всего 300МГц.
Эти процессоры примерно какому amd или intel равны по производительности? Что бы иметь представление.
Зато в серверных приложениях (коих становится всё больше) случайные задержки размножаются. Если 99% персентиль или даже 99.9% равен нескольким миллисекундам, а оставшиеся могут зависать на секунду, то типичный запрос пользователя, который дёргает сотню микросервисов будет иметь почти 100% вероятности попасть хоть на один тормозящий запрос. И в итоге все метрики будут почти в идеале, а у пользователей всё тормозит.
Да, но учтите, что рост быстродействия эту непредсказуемость делает несмертельной для большего и большего числа применений.
Во-первых, рост быстродействия CPU расширяет их сферу применения, вытесняя схемотехнические решения.
Во-вторых, нерентабельно использовать более дорогой быстродействующий CPU там, где вполне достаточно существенно более дешевого менее быстродействующего CPU с низким энергопотреблением и пассивным охлаждением.
В-третьих, объем RAM ограничен. А языки с GC по объективным причинам требуют больше оперативной памяти для своей работы. Например, на микроконтроллерах это может иметь решающее значение.
ниша для языков без сборщика мусора остаётся, но она сужается по мере роста производительности и развития разных GC
Как я уже указал выше, эта ниша так же расширяется по мере возникновения новых сфер применения там, где раньше из-за ограничений производительности можно было применять только схемотехнические решения, что могло приводить к нерентабельности производства таких изделий.
для GUI если средняя задержка отклика на событие занимает 10мс, а изредка есть пик до 200мс, то вы этот пик не заметите
В играх я сразу 30 мс замечаю. А есть игроки, которые заметят и 10 мс. А уж 200 мс любой заметит даже просто при наборе текста.
Во-первых, рост быстродействия CPU расширяет их сферу применения, вытесняя схемотехнические решения.
Я не вижу, честно говоря, каких-то особых новых областей, причём требующий быстрого отклика. Может быть робототехника, разве что.
Во-вторых, нерентабельно использовать более дорогой быстродействующий CPU
Они сейчас очень дёшевы. Десктопы уже точно с избыточным быстродействием, которое сплошь и рядом меняют на портативность (ноуты).
В-третьих, объем RAM ограничен.
Ой, в телефонах уже 12 Гб.
В играх я сразу 30 мс замечаю.
Ну числа же от балды.
Я не вижу, честно говоря, каких-то особых новых областей, причём требующий быстрого отклика.
Оглянитесь. Вокруг Вас множество таких устройств.
Они сейчас очень дёшевы.
Ага, PMS171B $0.02, STM32F103C6T6 $0.5, STM32F405VGT6 $2. А что предлагаете Вы? Ну хотя бы до $5.
Ой, в телефонах уже 12 Гб.
А в PMS171B 96 байт + 1.5К слов для кода. STM32F103C6T6 - 10К RAM и 32K флеша. STM32F405VGT6 - 192К RAM и 1M флеша.
Поинтересуйтесь, сколько памяти в Вашей СВЧ печи, стиральной машине или автомобиле.
На один CPU общего назначения для десктопа или ноутбука уже сейчас приходится добрый десяток микроконтроллеров и маломощных CPU. И с развитием IoT это соотношение только увеличивается.
Да только в ноутбуке, кроме CPU, есть множество микроконтроллеров. В том числе, требующих загрузки в них firmware. Или если не Вы для них софт пишете, то их не существует?
Это старые области.
Ага, IoT вчерашний день, компьютеры стали обходится без сетевых карт, смартфоны без GPS, аккумуляторы без контроллеров заряда и т.п. Вы из будущего пишете?
Каждый год появляется все больше подобных устройств таких, котрые без CPU, только аппаратными средствами, еще недавно делать было совершенно нерентабельно. Отсюда и МК с несколькими ядрами и тактовой 600 МГц, а требуется еще больше, так как гарантированного времени реакции системы не хватает. Даже если писать на C/C++.
Я не к тому, что они стали обходиться, нет. Я о том, что они и раньше не обходились. Соответственно, да, есть эта область, занятая С. В неё может придти московский лётчик, но это не новая область, это старая. Из неё придётся вытеснять С.
раньше не обходились
Раньше большинства таких применений просто не было. Либо по причине нерентабельности их реализации схемотехническими решениями, либо по причине физической невозможности реализовать схемотехническими решениям. И по мере роста производительности, все больше и больше возможностей открывается.
Например, для управления стаей дронов производительности многоядерных МК с частотой 600 МГц оказалось недостаточно. То есть, уже ML/AI уже стало востребовано в системах реального времени и требует все более и более производительных CPU.
Тот же автопилот для автомобилей - тоже система реального времени, ну никак не совместимая с непредсказуемыми задержками GC.
Раньше большинства таких применений просто не было.
Это всё должно отражаться на рынке труда. Я этого отражения не вижу — как во встройках не платили, так и не платят.
Исходя из средней годовой компенсации для Embeded Developper в $158K и для C# Developer в $131K, за последнее вообще не платят )
Или Вы по РФ судите, где разработка для встраиваемых устройств долгие годы находилась в зачаточном состоянии? Сейчас только дело сдвинулось, когда тот же Сбер ищет разработчиков встроенных систем на 450 тыс. руб в месяц. Или даже до 600 тыс. рублей в месяц в Алабуге.
Нет, я смотрю по Западу, где С++ник в трёхлитровой банке получает под 200. Но, возможно, я просто отстал от жизни.
РФ как раз в этом смысле явно в лучшем положении, чем Запад, всё больше и больше зарывающийся в виртуальности (и уничтожающий реальный сектор).
И кроме того, в РФ есть перспективы того, что с процессорными мощностями будет совсем швах, а это приведёт к тому, что программы действительно надо будет оптимизировать. На Западе же ничего подобного как-то не видно: всё делается в Китае, соответственно, там и программисты для встроек; у универсальных машин имеющиеся вычислительные мощности избыточны.
Нет, я смотрю по Западу,
Прошу прощения, но приведенные мной ссылки на glassdoor как раз и показывают размеры зарплат на Западе.
всё делается в Китае, соответственно, там и программисты для встроек
Тут Вы тоже сильно заблуждаетесь. Даже для РФ во многих случаях Китай выступает лишь OEM производителем, не утруждая себя конструированием и разработкой. А уж для Запада OEM производство в Китае явно превалирует над ODM.
Достаточно сделать поиск в github, например, по STM32, чтобы убедиться, что процентное отношение китайцев там невелико.
у универсальных машин имеющиеся вычислительные мощности избыточны
Я же приводил выше пример с управлением роем дроном, которое эти "имеющиеся вычислительные мощности" не позволяют до сих пор решить. Аналогичная ситуация складывается и с автопилотом автомобилей. Например Tesla FSD требует рассеивания 72 Вт, накладывая ограничения на сенсор всего в 1.2 мегапикселя. И для того, чтобы обойти уже известные ограничения требуются всё более мощные CPU.
Да, но учтите, что рост быстродействия эту непредсказуемость делает несмертельной для большего и большего числа применений.
Да, собственно весь комментарий об этом и был. Если предсказуемость несущественна то проще писать на языках с GC.
Самый тупой вариант — для GUI если средняя задержка отклика на событие занимает 10мс, а изредка есть пик до 200мс, то вы этот пик не заметите (тогда как на компьютере, работающем в 10 раз медленнее числа будут 100мс и 2 секунды — и пики очевидны).
Вариант не тупой, это просто игнорирование проблемы. Если 200мс на обработчике клика никто не заметит то 200мс обработки фрейма очень даже заметите при скроллинге. Даже в ворде/экселе а не в спортивном шутере. К сожалению ни в один язык/фреймворк не завезли аннотации/декораторы которыми можно указать что тут можно лагать а вот тут нежелательно.
можно мусор в пределе вообще не собирать
Я слышал о таком еще лет 15 назад, в основном это были HFT страдальцы которые повинуясь моде затащили Java-стек. Кто-то платил деньги за java-машины с "bleeding edge" GC типа azul. Кто-то просто выключал GC и ночью перезагружал кроном процессы. Узкоспециализированное решение, из разряда поставить кухню в здании склада со взрывчаткой.
ну или собирать только тогда, когда программа и так «заблокирована» каким-нибудь длинным вычислением.
Ну то есть просто управлять памятью вручную.
Ну то есть просто управлять памятью вручную.
Нет, имеется ввиду запуск Gc.collect
Я не знаю насколько это работает в C# но в остальных языках это метод без всяких гарантий. Можно назвать это пожеланием а не инструкцией. И в конце концов чем это лучше прямого вызова деструктора?
И в конце концов чем это лучше прямого вызова деструктора?
Ну хотя бы тем, что это убирает сразу много неиспользуемых объектов, а не один, пусть и включающий в себя другие, как вызов единичного деструктора; а главное, для чего сделаны сборщики мусора — хранением указателей на объекты не надо заморачиваться.
чем это лучше прямого вызова деструктора?
В однозадачном приложении это несколько упрощает жизнь разработчику по сравнению с C/C++, но не по сравнению с Rust. Цена такого упрощения:
необходимость прохода по дереву поколений, в дополнение к вызову деструкторов;
из-за неразрешимости проблемы остановки Тьюринга, деструкторы будут вызваны только для ставших недостижимыми объектов, так как GC не знает и не может узнать, какие из достижимых объектов можно удалять.
В многозадачном приложении, наоборот, сборщик мусора усложняет жизнь разработчику, так как блокирует все нити на непредсказуемое время. Особенно ярко эта проблема проявляется на серверных приложениях, где такие задержки могут вызывать лавинный эффект.
Как следствие, потребность в оперативной памяти высоконагруженных приложений на C# существенно больше, чем на компилируемых языках с непосредственным управлением памятью. Если приложению нужны десятки гигабайт памяти - это не проблема. А вот если сотни - это уже становится проблемой.
Если приложению нужны десятки гигабайт памяти - это не проблема. А вот если сотни - это уже становится проблемой.
Ну вот я веду к тому, что реальных задач, для которых действительно необходимы десятки гигабайт RAM почти нет, а тех, которым действительно необходимы сотни — этих просто по пальцам рук пересчитать можно.
То есть, да, на серверах ниша для языков с ручным управлением памяти есть, но она очень узка.
Ну вот я веду к тому, что реальных задач, для которых действительно необходимы десятки гигабайт RAM почти нет, а тех, которым действительно необходимы сотни — этих просто по пальцам рук пересчитать можно.
А Вы можете доказать это утверждение? Потому что я почему-то последние лет 20 сталкиваюсь постоянно с проектами, где нужны десятки и сотни гигабайт оперативной памяти.
Это и АСКУЭ, и оптимизация логистики для Почты России, и управление подвижным железнодорожным составом, и производство того же ЕвроХим. Без ML - требуются десятки гигабайт. Как только начинается ML - уже сотни. Мне сейчас спарки A100 с суммарным объемом 160ГБ видеопамяти мало, так как перемалывать нужно терабайты информации.
Потому что я почему-то последние лет 20 сталкиваюсь постоянно с проектами
Ну вот вас конкрентно они и загружают. По ML — да, там есть большие данные, но они перемалываются кодом на C/FORTRAN под управлением Питона. Причём код этот написан давно.
В остальном, банки и финтек на Западе вполне довольствуется Питоном же для бизнес задач. Ну по крайней мере из того, что я вижу вокруг.
Десктоп — ну пухнет, но если учесть, что по крайней необходимости достаточно чего-то уровня Haiku и Ворда, то там, если ужаться, хватит и российского Эльбруса + 1 гигабайт ОЗУ (или вообще сколько-то сотен мегабайт).
Я просил доказать Ваше утверждение:
реальных задач, для которых действительно необходимы десятки гигабайт RAM почти нет
Не вижу доказательства.
перемалываются кодом на C/FORTRAN под управлением Питона
Мы же говорим о разработчиках, а не аналитиках. Это аналитик средствами Python вызывает библиотеки на C/С++/FORTRAN. А разработчик эти библиотеки создает и поддерживает.
Десктоп
Что Вы имеете в виду? Если офисный терминал, с Chromium, то последний написан как раз на C++. Если игровой компьютер или рабочую станцию, то там уже десятки гигабайт RAM.
Не вижу доказательства.
Отсутствие не может быть доказано, у нас не Пролог, нет «гипотезы замкнутого мира».
А разработчик эти библиотеки создает и поддерживает.
Он это давно уже создал, и ушёл в «Верхнюю тундру».
Что Вы имеете в виду? Если офисный терминал, с Chromium, то последний написан как раз на C++.
Там почти всё по историческим причинам написано на С++. Но причины именно исторические.
Игры тоже на C# делают, см. популярный движок Unity. Да, получается хуже, чем Unreal, но убегать из Таркова это только помогает.
Отсутствие не может быть доказано
Я просил доказать Ваше утверждение
реальных задач, для которых действительно необходимы десятки гигабайт RAM почти нет
Тут явно говорится о результатах какого-то статистического исследования. Правильно ли я понимаю, что Вы сами признали своё утверждение ложным?
Он это давно уже создал, и ушёл в «Верхнюю тундру».
Опять догма? Доказать можете?
А вот я могу. Далеко ходить не будем. За последний год по 300 коммитов в среднем еженедельно. И что-то я снижения их количества не наблюдаю.
Игры тоже на C# делают, см. популярный движок Unity.
Вы что ли не в курсе, что Unity транслирует код C# в C++? Причем последний для WebGL еще и транслируется в JavaScript. Компиляция в CIL используется только для некоторых платформ и по явному пожеланию пользователя. Но так как весь runtime Unity написан на C++, это к заметным проблемам из-за GC не приводит:
"Почти нет" это в смысле что простому перекладывателю JSONов никогда не придётся их увидеть? Для бэкенда это не такие большие числа.
Десяток гигабайт памяти это всего десять тысяч параллельных запросов по мегабайту каждый. А мегабайт это вообще ни о чём сейчас: открываешь какой-нибудь facebook.com и первым же ответом прилетает полмегабайта, ещё до того как ресурсы грузиться начнут.
Если десять тысяч слишком страшно, то пусть будет тысяча по десять мегабайт, невелика разница.
"Почти нет" это в смысле что простому перекладывателю JSONов никогда не придётся их увидеть?
Ну типа да. Удел редких людей, пишущих маршрутизаторы.
Десяток гигабайт памяти это всего десять тысяч параллельных запросов по мегабайту каждый.
Так они даже если и попадают на одну и ту же машину, обрабатываются разными процессами (ну если по-уму делать). Дальше редискин кеш и общая база данных.
Да, люди, пишущие SQLite, Postgres (В. Вагнер, к примеру), Redis действительно должны о чём-то таком задумываться. А простой перекладыватель JSON'ов как пейсал на Питоне, так и пишет.
______________________________________________________
То есть, берём саксаулов (вроде вас, или покойного Куздры), нанимаем на full-time, заставляем их писать middleware на ЦэПэПэ или чём-то другом, что даёт возможность связывания с языками со сборщиком мусора. Перекладывальщики JSON'ов используют полученные middleware, может быть слегка учитывая какие-то его особенности, но не вникая, и пишут на языке со сборкой мусора и прочей радостью.
Поскольку сборщики мусора друг с другом взаимодействовать пока не умеют - нет стандарта, действительно этот middleware должен быть написан на языке с ручным управлением памятью, хоть на ФОРТРАНе, но обычно удобен Цэ.
И, кмк, именно отсутствие стандарта на GC и определяет то, что низкоуровневые библиотеки должны быть написаны на языке без GC. Все остальные доводы в пользу языков с ручным управлением памятью на серверах уже не очень состоятельны. Низкая латентность на серверах нужна только у высокоскоростных спекулянтов, памяти можно добавить, базы данных распилить между серверами. Кругом микросервисы => минимум латентность 1 миллисекунда на сетевой запрос на хорошей сети.
Ну так перекладывальщик JSON и не является целевой аудиторией Rust. Это язык для бэкенда, для ядра и т.д. И среди этой группы десяток гигабайт памяти уже не такая редкость. И пауза в 10ms, а если не повезёт то и в 100ms, тоже не только для HFT важна.
Это язык для бэкенда, для ядра и т.д.
В смысле, для ядра бэкенда, для относительно низкоуровневых библиотек? Просто бэкенд делается на Питонах сейчас (умело совмещая недостатки всевозможных подходов).
Если бэкенд можно написать на питоне, то это не бэкенд а так, бэкендик. А я про настоящие :)
Или,в редких случаях, людям настолько нужен именно питон для специалистов в предметной области и прочих ресерчеров, что ради этого они готовы героически преодолевать и лишаться. Но это редкий вариант.
А я про настоящие :)
Узок их круг, и далеки они от народа. :-( Я тут «настоящих» не вижу — всё решается языками с GC, может быть за исключением подписки на market data, хотя как её пишут...
В общем, практически во всём, от GUI до серверных программ разной разности, всё прикладное может обслуживаться языками со сборкой мусора. А Rust и С++ привносят лишь разные сложности.
Может обслуживаться чем угодно, безусловно. Я даже вспомнил один довольно большой и успешный бэкенд на питоне, правда в нестандартной инкарнации: Eve Online, по состоянию на десять лет назад. Другой вопрос, что это очередной случай когда выбор простого инструмента позволил компании вообще не умереть вначале, но потом заставил тратить кучу денег на последствия этого решения. Два стула, так сказать.
Я в общем-то не спорю, что большая часть задач в IT может быть решена на самых разных языках, включая PHP и JS. Так же как большая часть автоперевозок может быть осуществлена на чём попало, и вообще большая часть чего угодно в любой отрасли может быть сделана из подручных материалов. Сарайчиков строится гораздо больше, чем небоскрёбов, и инструменты для них нужны разные.
выбор простого инструмента позволил компании вообще не умереть вначале, но потом заставил тратить кучу денег на последствия этого решения.
Проблема в том, что это общий принцип, от которого крайне редко отходят. Даже когда помимо двух стульев есть простой табурет.
Нет, выбор простого и ограниченного инструмента позволяет легко набирать раб. силу, а сколько они там пейсать будут, никто не считает. Железо же, хотя бы в теории, позволяет не беспокоиться насчёт принципиальной реализуемости большинства задач. Таков менеджеризм сейчас — он мне напоминает «жадные алгоритмы», если честно.
Кругом микросервисы => минимум латентность 1 миллисекунда на сетевой запрос на хорошей сети.
Замерил в двух наших ЦОД по установленному двунаправленному потоковому gRPC. В обоих латентность 0.2-0.4 мс.
Причем не могу сказать, что сеть у нас там хорошая. Большинство серверов на 40GBASE-CR4 (по меди).
А если измерять латентность между подами одного хоста k8s, то получается 0.03-0.05 мс.
Даже так, хотя у нас всё это минимум 1мс на запрос/ответ в одном ЦОД, это на 4.5 порядка медленнее тактовой частоты (беру ваши лучшие данные), на 3 порядка медленнее выделения памяти на стеке, скорее всего на порядок-другой медленнее выделения памяти в куче.
Ну и если это микросервис, мы получаем «котлеты из рябчиков пополам» — один рябчик + один конь.
на 4.5 порядка медленнее тактовой частоты
Тактовая - это попугаи.
на 3 порядка медленнее выделения памяти на стеке
Память в стеке выделяется на этапе компиляции. В коде это одна команда сложения указателя стека с константой для всех переменных в стеке.
порядок-другой медленнее выделения памяти в куче
Проблема языков с GC не в выделении памяти, а в её освобождении. Накладные расходы free/delete на два-три порядка ниже, чем у GC. Одно дело, освободить конкретную область памяти. А совсем другое, пройтись по дескрипторам всех ранее выделенных участков памяти, чтобы найти уже недоступные программе.
Попробуйте на сложном сервисе посмотреть время выполнения GC в C#. Получите несколько миллисекунд, что уже на два порядка больше латентности между подами одного хоста k8s.
Можете не пробовать, а поверить MS: "Ephemeral garbage collections (generations 0 and 1) last only a few milliseconds"
Попробуйте на сложном сервисе посмотреть время выполнения GC в C#. Получите несколько миллисекунд, что уже на два порядка больше латентности между подами одного хоста k8s.
Ну и отлично. Вы же не собираетесь принудительно делать сборку мусора на каждый ответ микросервиса.
То есть, если у вас микросервисы, то о жёстком реальном времени можно, в общем, забыть. Соответственно, эпизодические несколько миллисекундные задержки не играют роли.
Более того, можно в очередь сообщений микросервиса вставлять сообщение на принудительный сбор мусора в свободное от ответов время. Можно, как у наших сделано, вызывать Gc.collect после отсылки ответа - это же не единственный запущенный экземпляр сервиса.
эпизодические несколько миллисекундные задержки не играют роли
Докажите это, например, для АСУТП.
Вы реально АСУТП делаете на микросервисах? Вот этой классической микросервисной архитектурой?
Но раст ведь можно выбирать не только из-за работы с памятью и отсутствию gc. Лично мне он приятен очень хорошей инфраструктурой, простым стартом и нормальной системой типов. Просто приятный язык, который даёт хорошие гарантии для твоего кода. А память и производительность только приятный довесок.
Рейтинг статьи скачет, то плюс, то минус. Идёт борьба и покой нам только снится. :)
Вот этот аргумент
То есть вы не сможете выделить память и просто забыть её освободить, тем самым вызвать утечку памяти.
как говорится, "доставляет".
А не пробовали решетки на окна, стены матрасами обить и кушать только пластиковой ложкой? А то ведь не дай бог - можно из окна выброситься, головой о стену убиться, вилкой уколоться...
Утечка памяти - плохо. Но у вас должно быть средство из диагностировать (помнится, в Borland C++ Builder такое было - запустил программу - после завершения списочек - у тебя не освобождается выделенная тут, тут и еще вот тут память.
Не надо думать, что разработкой будут заниматься олигофрены с тремя классами церковно-приходской школы за плечами. Это таки занятие для людей, обладающих требуемым уровнем квалификации. Которые должны нести полную ответственность за то, что они делают. Написал плохой код в результат которого заказчик понес убытки - компенсируй из своего кармана. И сразу поубавится желающих "получать 100500 в час лабая говонокод левой ногой".
И да. Средства разработки должны давать возможность писать безопасный код. И для этого не должно быть нужно выворачиваться мехом внутрь. Это должно быть просто и естественно. Но при этом все это не должно тащить за собой огромных накладных расходов в рантайме и это не должно быть диктатом. Считайте разработчика умным и ответственным и помогайте ему, но не диктуйте что и как делать.
Как вы думаете, почему в С++ появились смарт-поинтеры? По вашей логике ими пользуются только низкоквалифицированные разработчики, которые не умеют запускать санитайзер?
Я не знаю. Наверное, кому-то и зачем-то это было нужно. Но это не значит, что это нужно всем, всегда и иначе нельзя.
Я против обязательности. Но если ты что-то делаешь - будь добр внятно объяснить почему именно так, что это дает и какие риски несет. Если можешь - пользуйся. Нет - возьми пластиковую ложку. Ей точно не поранишься.
Т.е. смарт-поинтеры не нужны, понял. Скажите, а зачем вообще нужен С++, если есть С?
Т.е. смарт-поинтеры не нужны, понял
Покажите где я сказал что они не нужны? Не надо опровергать то, что не было сказано.
Наверное, кому-то и зачем-то это было нужно.
Но огромное количество кода было написано (и, что характерно, до сих пор работает) на языках, где их не было. Из чего могу предположить, что это удобно, но не является тем, без чего нельзя жить.
Вообще, если говорить строго, чем дальше вы отходите от "железа" и чем больше вводите уровней абстракции, тем больше все это тащит за собой накладных расходов. И тут уже в каждом конкретном случае надо решать - можете вы в данной задаче себе это позволить, или нет.
Если вы пишете "тяжелый" код и с вас за это начнут вычитать те суммы, которые заказчик будет вынужден потратить на апгрейд железа чтобы ваш код на нем работал, вам придется научится или писать "легкий" код, или "переквалифицироваться в управдомы".
А давно умные указатели делают код тяжелее?
Но огромное количество кода было написано (и, что характерно, до сих пор работает) на языках, где их не было
Мы говорим конкретно про С++. И вопрос был "зачем их сделали если есть санитайзеры". Чего вас куда-то в сторону несет?
Вообще, если говорить строго, чем дальше вы отходите от "железа" и чем больше вводите уровней абстракции, тем больше все это тащит за собой накладных расходов.
Ага, понятно, так а зачем же тогда отошли от "железа"? С++ какой-то выдумали, хотя уже был близкий к bare metal язык С. По вашему, видимо, Бьёрн Страуструп сделал язык для "олигофренов с тремя классами церковно-приходской школы за плечам", которые не могут нормально malloc и free вызвать и данные от логики отделить.
Если вы пишете "тяжелый" код и с вас за это начнут вычитать те суммы
И насколько borrow check раста добавляет оверхэда для управления памятью? Извините, а вы вообще хоть одно строчку в своей жизни на Rust написали или просто "не читал, но мнение имею"?
Так ведь никакой обязательности в Rust и нет, лично вам писать ключевое слово unsafe никто не запрещает, и сырые указатели из языка никого не удалял. С чем вы с спорите-то?
Вы не поверите, но все дома строят с окнами и проёмами и прочими объектами на высоте таким образом, чтобы не было риска случайно вывалиться, включая минимальную высоту нижней стороны проёма, отсутствие "подножек" позволяющих наступить и перескочить и т.д. Углы тоже отмечаются, когда есть риск о них убиться и никто не допускает торчащей произвольно арматуры, да и бегать с ножами плохая идея, когда-нибудь наблюдали за работой профессионалов с максимально острыми ножами? Чрезвычайное уважение к инструменту в процессе.
Не надо думать, что разработкой будут заниматься олигофрены с тремя классами церковно-приходской школы за плечами
К слову о токсичности, это, видимо, как раз пример не токсичности. А вот статьи и комментарии, что Rust во многих аспектах лучше языка X это да, прям токсичная токсичность, и как только люди это терпят, это же гораздо хуже, чем других олигофренами называть.
Вообще все равно. Я из мира JVM и на Rust писать готов (в том числе embedded), на C/C++ нет.
А так... Думаю, что в момент появления C примерно такие же мысли (C не нужен) были у программистов на ассемблере.
А rust годов к embedded?
Смотря что вы вкладываете в термин готов. Поддержка архитектур есть для arm, risc-v, atmega с различным tier. PAC и HAL тоже есть. Инфраструктура в виде IDE и HAL для C сильно лучше в силу ее поддержки вендорами типа STMicroelectronics. Новые игроки типа Espressif Rust поддерживают сами, насколько я могу судить.
В целом мне сложно сравнивать, т.к. embedded я только с Rust занимался. Мне нравится, особенно на уровне работы c PAC, а не HAL.
Сыровата ещё, поддержка чего нибудь типа ble или usb так себе, но если они не нужны, то можно прекрасно писать код под контроллеры. Есть вот прямо хорошие штуки типа Embassy.
Так же как c++ - если выкинуть stdlib. А то неосторожно пару шаблонов заюзаешь и code bloat всю память MCU выжрет )
пишет код на С++ попивая своё винишко с Мальдив:)
Вино на Мальдивах не производят, только экспортируют
А меня удивляет в 2024 году, что до сих пор пишут про два совершенно разных языка, как про один "написаны на С\С++". Они же совсем разные, ладно лет 30 назад, но сейчас из общего по моему только синтаксис и интероп.
Половина либ в мире это С, остальная половина С++.
Разными они станут когда разойдутся окончательно и код на С невозможно будет собрать С++ компилятором (по мне, так это надо было сделать уже давно - мухи должны быть отдельно, а котлеты отдельно)
А это так и есть с самого рождения C++. Пример:
void foo();
void bar() {
foo(0); // valid in C, invalid in C++
}
Как раз для этого, удобно собирать С проект, С++ компилятором, что бы исправить всё легаси.
Это не легаси, такие декларации в C никто не отменял. Легаси - это определения параметров вне определения функции.
Вот ещё C и не C++
int a[3] = { [2] = 3, [1] = 6 };
Вот за такой код ручки то надо об батарею отопления, советскую, ровнять. Что так можно, не значит, что так нужно. Обфускаторить код можно и более изящно.
А да, вынос аргов такой, это строго сишный прикол, и не совсем в кассу.
Ох уж эти академические примеры. Вы бы хоть имеющуюся в стандарте C комплексную арифметику привели б в пример, или ещё что-нибудь, что показывает, кусок какого-нибудь стоящего (и с точки зрения работы, которую он выполняет, и с точки зрения подражания технике кодирования), который не прожуётся компилятором C++. А то, что вы привели, так совершенно точно не нужно писать, даже если так можно.
Новый код так и не пишут. А старый такой код есть. Если нужен стоящий пример — берём redis, пытаемся собрать плюсовым компилятором, не получается на втором же файле:
adlist.c: In function 'list* listCreate()':
adlist.c:25:24: error: invalid conversion from 'void*' to 'list*' [-fpermissive]
25 | if ((list = zmalloc(sizeof(*list))) == NULL)
| ~~~~~~~^~~~~~~~~~~~~~~
| |
| void*
adlist.c: In function 'list* listAddNodeHead(list*, void*)':
adlist.c:74:24: error: invalid conversion from 'void*' to 'listNode*' [-fpermissive]
74 | if ((node = zmalloc(sizeof(*node))) == NULL)
| ~~~~~~~^~~~~~~~~~~~~~~
| |
| void*
adlist.c: In function 'list* listAddNodeTail(list*, void*)':
adlist.c:107:24: error: invalid conversion from 'void*' to 'listNode*' [-fpermissive]
107 | if ((node = zmalloc(sizeof(*node))) == NULL)
| ~~~~~~~^~~~~~~~~~~~~~~
| |
| void*
adlist.c: In function 'list* listInsertNode(list*, listNode*, void*, int)':
adlist.c:133:24: error: invalid conversion from 'void*' to 'listNode*' [-fpermissive]
133 | if ((node = zmalloc(sizeof(*node))) == NULL)
| ~~~~~~~^~~~~~~~~~~~~~~
| |
| void*
adlist.c: In function 'listIter* listGetIterator(list*, int)':
adlist.c:197:24: error: invalid conversion from 'void*' to 'listIter*' [-fpermissive]
197 | if ((iter = zmalloc(sizeof(*iter))) == NULL) return NULL;
| ~~~~~~~^~~~~~~~~~~~~~~
| |
| void*
Если продолжить собирать дальше, можно наткнуться на restrict
, переменную int delete
, параметр функции const char *template
, это даже никаким -fpermissive
не убирается. И это у меня больше половины файлов не начали компилироваться, потому что Lua не установлен.
Вопрос ни в том, что произвольно взятый код на C будет компилироваться плюсовым компилятором. А в тех усилиях, которые надо приложить, чтобы он начал компилироваться. И эти усилия на самом деле минимальны, с точки зрения общих затрат на разработку. Думаю в приведенном примере с redis, это все чинится максимум за человеко-неделю разработчика.
Собственно мало, кто знает/помнит, но основной sailing point плюсов в конце 80-х начале 90-х был - "вы можете не выбрасывать свою сишную кодо базу, а начать постепенно переносить ее на плюсы".
Ну уж нет, когда кто-то заявляет вещи вроде "код на Си можно собрать компилятором С++" - вопрос именно в том, может ли произвольно взятый код на Си собираться компилятором С++.
Я вам даже больше скажу: не любой произвольно взятый код на С++ будет собираться другим (более новым или другой церкви) компилятором С++. Поэтому плюс/минус ваш пример можно засунуть в этот же кейс. Поменять названия идентификаторов, чтобы они не попадали на ключевые слова - с этим и обезьяна справится.
Если нормально писать код и думать, что он в принципе будет собираться плюсовым компилятором - все будет хорошо. То что есть какое-то количество edge case, которые не будут работать - да это так. И что? Какое это отношение, вообще имеет к практической разработки?
Если даже сишный код успешно скомпилируется компилятором С++, не факт, что он будет работать так же.
Этот поинт, тёзка, блин, мне до сих пор икается, так как предпочитаю писать код как на С, но структуировать, как на плюсах. Знатное извращение когда пишешь не наколленный код, и менять надо весь стиль и мышление при написании кода.
Да ладно, код на Си одного компилятора не всегда можно собрать другим компилятором :)
Совместимость с С это убер фича. Думаю, что никогда не разойдутся, возможно добавят в стандарт некоторый режим, который бы убирал совместимость, упрощал язык С++. Что то подобное.
C++ - расширение языка C, открываю вам большой секрет
Вообще странно видеть среди задрачивателей евангелистов C/C++ тех, кто даже не знает сей факт. Как вы живёте с этим?
Открою и вам секрет. Есть такое понятие как аналогия. Как вы живете без знания этого?
Возможно реально, после каждого абзаца, открывать скобочки и писать, с каим смысловым посылом я это написал. Вроде [сарказм], [аналогия] и т. д
C++ - расширение языка C, открываю вам большой секрет
C++ уже давно не является надмножеством C
Ага. Только new упорно заканчивается банальным malloc'ом... Ну да, ну да. Не является.
Давайте вы сначала разберётесь в том, что такое "надмножество", а потом уже будете писать глупости.
Начиная где-то так с C99 языки разошлись в разные стороны. Выше вон уже приводили кучу примеров, когда код на C нельзя собрать C++-компилятором, либо когда один и тот же код в C и C++ может иметь разное поведение. Так что не, уже не является, как бы вам не хотелось обратного
Но тогда как быть котлете++, когда нужно общаться с системой. Я видел конечно например реализацию DDL as Plugin на плюсах, но Си наследие от туда не вынешь. Не будет работать.
В целом я сам придерживаюсь вашей позиции, но не вижу как конкретно это реализуемо.
и код на С невозможно будет собрать С++ компилятором
Это уже давно произошло, начиная ещё так с C99. И ещё до этого, некоторые вещи, допустимые в Си, являлись и являются UB в C++ - код скомпилируется, но корректность работы вам уже никто не гарантирует.
Зачем? Я люблю ещё С++ за то, что могу в нем писать в простом стиле С, как в высокоуровневом ассемблере.
Я пилю проект LDL, если вкратце это аналог библиотеки SDL, но в том числе с поддержкой старых систем и главное преимущество в том, что LDL имеет единый API <...>
В своё время пользовался преотличнейшей библиотекой от Zinc для создания GUI. Почему то уверен, что вам захочется глянуть исходники.
http://www.openzinc.com/Screenshots.html
![Список поддерживаемых систем и компиляторов. Список поддерживаемых систем и компиляторов.](https://habrastorage.org/getpro/habr/upload_files/577/cf0/504/577cf0504318a9a008eadbb701e8bbe8.png)
Выглядит достаточно олдово:) Спасибо за ссылку. Исходники обязательно посмотрю.
Список компиляторов и систем внушительный. Умели же. И ведь работало и на 286 процессоре с десятком mhz. Знания древних.
Внутри, скорее всего, то, что современные апологеты назовут "говнокодом".
Не читал, но скорее всего, так оно и будет. Всё потому, что воспеваемая автором мультикультурностькомпиляторность есть ни что иное как самоцензураограничение до общего подмножества языка, что, учитывая неразвитость компиляторов времён керниганозоя, оставляет довольно убогое подмножество (
Да всё верно. Это только ограничения для меня, как разработчика библиотеки. Пользователь библиотеки может юзать любой современный компилятор и новый стандарт. И ещё в планах реализация современного врапера над библиотекой, к примеру для C++ 17.
Ну, если сам код написан нормально (по меркам керниганозоя), то это всё-таки не говнокод, а легаси.
Проблема в том, что современный стандарт позволяет писать более эффективный с точки зрения перфоманса код. Например, если в дело идёт move-семантика. А автор либы на подмножестве лишен этого всего. Так что мерки керниганозоя на красоту кода натянуть можно, а вот перфоманс - увы.
Графику рисует видеокарта, просто юзаю API opengl, под капотом абстрактного 2D API поэтому move семантика тут ни как не даст перформанса. Но да, остальные участки можно ускорить.
LDL работает на Ms dos 16 бит и памяти требует примерно 45 кб библиотека + 64 кб буфер ну и ещё 20 кб на внутренние буферы.
Уже и не знаю, за счёт чего повысить производительность. Улучшить софт рендер только.
(ошибся веткой)
Ну вот вы говорите "Стандарты". Просто открыл вашу библиотеку и ткнул в случайный файл, в котором по имени ожидал какую-нибудь не тривиальную логику.
По стандарту можно не проверять на NULL перед delete. А вы проверяете. Это попытка обхода того, что какой-нибудь из старых компиляторов не знает про стандарт? Кроме того, не вижу, чтобы const-методы были помечены как const. Тоже поди какая-нибудь обходка того, что не поддерживается другими?
Ну мне казалось, что такие вещи у джентльменов разруливаются макросами )
Нет, я не хочу сказать, что вижу говнокод -- по крайней мере он написан опрятно. Это у меня пассаж в сторону того, что стандарты нам только снятся, а на деле мы вообще имеем ещё более узкое подмножество - пересечение стандарта и навыка программиста по использованию фишек стандарта.
Это попытка обхода того, что какой-нибудь из старых компиляторов не знает про стандарт?
Всё верно. Как и данный случай. Всё ради поддержки Borland C++ 2.0 Как бы это не выглядело безумно в 2024:)
Кроме того, не вижу, чтобы const-методы были помечены как const. Тоже поди какая-нибудь обходка того, что не поддерживается другими?
Это уже я не уследил. Надо будет порефакторить проект на эту тему.
Ну мне казалось, что такие вещи у джентльменов разруливаются макросами )
Стараюсь не использовать макросы. Использую когда вообще невозможно без них обойтись.
Неоптимальный код всё ещё не говнокод. И потом, от древней UI библиотеки я бы ожидал в первую очередь не тормозов, а отсутствия адаптивности интерфейса. Однако, отсутствие фичи тоже не делает код говнокодом.
Ну, если сам код написан нормально (по меркам керниганозоя), то это всё-таки не говнокод, а легаси.
Я не жду пока мой код станет легаси, я сразу пишу легаси код:)
Прям TK'ем повеяло. Спасибо, что напомнили. Руки прям зачесались что-нибудь на TCL/TK написать :)
Главный минус rust это выпиливание поддержки "устаревших" ос, не так давно windows 7 списали. А эта ос ещё до фига где стоит, и поддерживать её надо. В инфобезе например требуется запускать агентов на старых ос... А gcc например можно собрать под очень старые ос и использовать код например с 17 стандартом.
Расскажите плиз, как юзать современный С++ под древние системы? Кросс компиляция?
есть свои пределы конечно, но например собрать gcc 9.3 можно без проблем под FreeBSD 12.0, CentOS 7, Windows XP (mingw). Кросс компиляция про архитектуру.
Я читал о таком способе. Пишут на современном С++, после транслируют в ассемблер и уже этот ассемблер транслируют поддерживаемой версией, tasm или masm. Сразу скажу, я сам не экспериментировал.
Так со всякой древнотой проблема во всяких концепциях типа far-pointer для понимания которой я слишком молод.
Как-то колдовал кросс-платформу на эмбеде и кучу раз пнулся о 16 битные инты которые вырастают тут и там.
Far и near концепция не сложная. Так как LDL собирается из одной репы, завёл файлик config.hpp в нём не только макросы переопределяющме тот или иной функционал для совместимости кода для разных компиляторов, но и переопределение типов из stdint.h
В итоге всё собирается в том числе и под Ms-dos.
Я такой концепт выбрал для лучшей портируемрсти кода. Конечно это сильно ограничивает меня как разработчика, нет шаблонов и всяких плюшек. Но работает.
Смотря насколько современные плюсы и насколько старые системы. Под виндой, например, 141 тулсет вплоть до хр работает, это с++17. 143 от висты (емнип) и выше, это c++latest. С до хршными системами уже сложнее.
Так rust под windows 7 собрать тоже можно, по крайней мере попытаться.
https://doc.rust-lang.org/nightly/rustc/platform-support/win7-windows-msvc.html
Могу это подтвердить, сейчас пишу свой проект используя windows 7 (rustc 1.77.1 (7cf61ebde 2024-03-27)). Причем при компиляции используется не вышеупомянутый таргет, а обычный x86_64-pc-windows-msvc
, из-за чего у меня в одной библиотеке не сработал cfg
на этот таргет и ничего не работало.
Да и судя по этому комментарию все не так плохо.
На небе только и разговоров что о море... Обе экосистемы успешно развиваются и это хорошо. Я писал раньше на C/C++, больше не пишу - по ряду причин совсем не связанных с самими C/C++, а вот на Rust пишу и достаточно много. Я думаю, что оба языка достаточно сложны, а на уровне "мастерства" особо сложны. Недавно видел код на Rust, который проще сразу выкинуть, чем даже пытаться прочитать. Видел много кода на C/C++, который титанически сложно понять.
Высказывания о сообществе Rust во многом справедливы - присутствует и чванливость и заносчивость и детские обиды и кэнцелинг. Я когда пытаюсь понять что случилось, часто даже понять не могу в чем проблема. Именно поэтому, часто есть статьи в духе "щас попробую объяснить...".
Однако, хочу поделиться своим процессом, который я прошел за последние 2 года, пока пишу на Rust. Изначально, интерес был академический - в духе осилить что-то современное и быстрое. Я смотрел на Golang, однако код на языке мне просто "внешне" не понравился. Rust казался чем-то необычным. Не скажу, что я много что написал - несколько FFI-библиотек для Python. Тем не менее, для меня есть несколько критериев, делающих язык удобным или неудобным.
Отладка. Я ненавижу отладку. С Rust, если скомпилировалось и тесты проходят - скорее всего проблем в духе SIGSEGV не будет, гонок не будет, UB не будет.
Архитектура кода. Язык поощряет думать над дизайном кода. Если не думать, будет боль. Как только чувствуете боль - с высокой степенью вероятности что-то делаете не так.
Реализация многопоточности. Многопоточность реализована просто и органично. Я, вообще, не люблю всякие TMTOWTDI в духе Perl5, писал много на этом языке и помню хаос, который в возникает в коде.
Реализация асинхронности. Несмотря на наличие определенных сложностей, дизайн того, как реализована асинхронность очень понятный и просто воспринимается.
Интегрированность разработки. Я люблю писать тесты - мне нравится, что я могу писать тесты по месту, исполнять их и контролировать сложность. Мне действительно нравится как это сделано в Rust - лучше чем у других языков, которыеми я пользовался (C, C++, Python, Scala, Java).
Уверенность. Все вышеперечисленное дает мне уверенность в том, что мой код действительно делает то что нужно. Можно сколько угодно говорить о том, что можно и в A и в B и в C быть уверенным, однако я никогда раньше не чувствовал такую уверенность в правильности своего кода как в Rust.
Тулинг. Мне нравится, что все под рукой - сборщик, линтер, чекер, форматтер, инструмент анализа кода на антипаттерны (clippy), бенчмаркинг. Просто не надо думать - бери и пользуйся.
Важно то, что большая часть кода, порождаемая экосистемой именно такая - это сильно повышает качество библиотек в части их надежности и дизайна. Этим он отличается от других экосистем - поощрением того как надо делать.
Я считаю, что это реальный геймченжер - это как настраивать сетевой маршрутизатор на базе Cisco и Linux - сделать можно все то же, но в случае с Cisco другой человек придет и разберется быстро. В случае с Linux все можно сделать совершенно разными способами, что затрудняет траблшутинг настроек.
В общем, я дико доволен тем, что такой язык появился. Для меня самые большие недостатки - время компиляции и то, что JetBrains бросили плагин для PyCharm и переключились на RustRover, который сильно кривой и от версии к версии вносит новые и неочевидные баги.
Это все субъективно, конечно, вполне допускаю, что люди которые давно и продуктивно пишут на C++ вообще не видят аргументов для перехода на Rust. Однако, и я не вижу ни одного аргумента в пользу того, что сейчас надо писать новые программы на C/C++, а уж тем более изучать его новым разработчикам, если это только не какая-то нишевая история.
Я думаю, что отношение формируется набором базовых установок, которые у вас есть. Если вы считаете что надежный, безбажный код - высший приоритет, вероятно, Вы будете смотреть на Rust. Если ваши базовые установки другие, то все аргументы на вас впечатления не произведут.
Если вы считаете что надежный, безбажный код - высший приоритет, вероятно, Вы будете смотреть на Rust. Если ваши базовые установки другие, то все аргументы на вас впечатления не произведут.
У меня было немного не так. Я делаю много разных своих pet-проектов. Раньше использовал Java. Но когда перешел на очень дешевую виртуалку с 1Гб памяти, то даже простое Hellos World на спринг буте там было не запустить не настроив своп.
Синтаксис Go просто не понравился поэтому остановился на Rust и тут понеслось... Теперь это мой второй любимый язык.
Сейчас на этой виртуалке запущено уже около 15 микросервисов. При этом каждый кушает 3-6Мб оперативки, что с Java, конечно, недостижимо.
На самом деле, многие в Раст с плюсов приходят, особенно это было в первые годы - я много таких людей видел, да и сам из таких.
Просто в какой-то момент уже подзадолбало воевать каждый раз с тулингом плюсов, страдать из за всякого legacy, дебажить сегфолты и ловить проблемы с многопоточностью.
Уж даже с borrow checkerом воевать оказалось не так стрёмно, как с сегфолтами. Хотя обидно иногда, что он не даёт скомпилировать вполне себе правильный код, особенно в асинке.
Ну и мне повезло, что не надо работать с графами.
Для меня самые большие недостатки - время компиляции и то, что JetBrains бросили плагин для PyCharm и переключились на RustRover, который сильно кривой и от версии к версии вносит новые и неочевидные баги.
Если это тот же плагин, что и в CLion, то не бросили. Было много разговоров на эту тему, потом они выпустили другой плагин, на который нельзя было автоматом обновиться - требовались какие-то действия руками. С тех пор этот новый плагин поддерживается, только сегодня вот обновился на очередную версию.
Я так понимаю это старый плагин, а это новый, который нужно вручную ставить? По сравнению со старым в работе хуже не стало?
Ключевая разница в том, что старый плагин был опенсорсным и ставился на любую JB IDE, а новый закрытый (емнип) и ставится только в платные версии. Это сделано, чтобы сгонять народ покупать RustRover или другие платные продукты (IDEA Ultimate, CLion и тд), не позволяя писать на Rust на бесплатном коммьюнити издании
Я с присущей мне объективностью и богатым опытом написания взвешенных и полностью наполненных пруфами статей, хотел бы указать автору на его неточности.
Эмоционально окрашенные картинки, к примеру, с измерением носа - это ещё объективность или уже пруфы?
Так вот, у вас, а не у нас есть некий мануал, а вот уже у С++ программистов имеется не просто мануал, а целый международный стандарт.
Ты кусок говна в котором полно неопределённого поведения, который поддерживается компиляторами как попало, хоть стандартом назови, эталоном он от этого не станет.
Это было сделано именно для этого, что бы у компилятора было больше свободы оптимизации. Такое решение, но тогда оно было оправдано. Важен контекст.
Что деды воевали не является оправданием нам сейчас жить по дедовьи и совершать те же дедовские ошибки.
Раздел про инфраструктуру не выдерживает критики если честно.
> Обычно просто берут одну систему сборки, и один пакетный менеджер и пилят проект. То есть в начале было много утилит, но потом сузили до двух распространённых. Это ещё не стандарт, но программы, имеющие широкий функционал, сообщество и поддержку. Это не то, что вы искали?
Нет, не то.
Если у проекта множество зависимостей, всё равно замучаешься хоть с жутким CMake хоть с монстроузным Bazel, хоть с чем, потому что авторы библиотек зачастую лепят кто во что горазд. Инфраструктура вокруг сборки проекта превращается в какой-то ад с кучей скриптов, подпорок и костылей.
Когда-то писал CMake конфигурацию для одного проекта, старался всё аккуратно и красиво сделать на modern cmake и т. п., чтобы собиралось везде и у всех разработчиков максимально просто и унифицировано. После меня пришел в проект какой-то суровый C-программист с "30-летним стажем", выкинул всю мою работу и наколбасил как привык, как 20 лет назад делали, чтобы проект только у него одного в особом окружении собирался, потому что ему так удобно, и он так привык. Не представляю себе такую ситуацию в проекте на Rust (или с любым другом современном языком с современным build-тулчейном из коробки).
Либо вы уже ушли, и вам пофиг что он там сделал, либо у вас не работает код ревью.
К сожалению, я работал с C/С++ только в школе и университете. Мне он нравился гораздо больше, чем Assembler, Basic или Pascal, и это было 25 лет назад. Но на 2-3 курсе университета меня соблазнил язык Java, и я ушел в него с головой. В итоге я остался разочарован, так как позже основал свой стартап и выбрал PHP для своего сервиса по созданию сайтов. После этого я снова вернулся в мир Java и с удовольствием перешел на Scala. Однако я снова начал искать универсальное средство и теперь погрузился в Rust. Мне это нравится.
Скорее всего меня сейчас заминусуют, но "ведь, если звезды зажигают —
значит — это кому-нибудь нужно?" (с) Маяковский В.М.
Ну не бывает "золотого молотка". Если кто-то пишет на C/C++ или Rust - значит на это есть объективные причины. То есть, в каких-то случаях лучше Rust, а в каких-то - C/C++.
Я даже знаю пару из этих случаев, хотя только недавно столкнулся с Rust. В PostgreSQL есть процедурный язык plrust, позволяющий просто через CREATE PROC создать процедуру на Rust, которая будет автоматически скомпилирована в машинный код при создании. И тут безопасность и широчайшая диагностика Rust действительно жирный плюс, не позволяющий легко и просто обвалить СУБД ошибкой пользователя в хранимой процедуре или функции. С/С++ такого предоставить не могут.
С другой стороны, есть работа с кортежами в страницах БД, когда мы заранее просто не знаем, не только смещение поля в записи и, тем более, в странице, но даже количество полей (привет NULL маска), их длины и не вынесены ли они в TOAST. И тут адресная арифметика в C/С++ позволяет оперировать сразу с нужными типами данных, а не рассматривать страницу БД исключительно как массив байтов.
И лично мне совершенно неинтересно, что лучше в общем случае "сферического коня в вакууме" - Rust или C/C++. Мне интересно когда следует выбирать тот или иной язык и по каким причинам.
Я понимаю о чëм вы говорите.
И вот, у меня большой проект на Python и JavaScript :) За эти языки (платформы), теперь, в смысле, сейчас, на данный момент, любого порву! :P
Я фуллстек разработчик, со всеми вытекающими. И тоже приходится знать кучу языков и технологий, фреймворков и остального вебовского. И знаете, я хотел бы всё это упразднить в какой то единый язык. Что бы не приходилось на каждой новой или другой системе, платформе разрабатывать на нескольких других языках.
У вас питон и js. У меня c# и js.
Автору, мне кажется, сильно повезло работать с каким-то небольшим подмножеством C++ на своих проектах. В крупных проектах ситуация отличается.
Всё упирается в то, можно ли написать большой полезный проект большой группой людей с полным соблюдением стандарта C++. Я утверждаю, что это невозможно, особенно если есть код старше 5-10-20 лет, см. ниже.
Да, это хорошо и большое количество С++ компиляторов обусловлено временем появления С и С++, это примерно 40-50 лет назад
А чем конкретно хорошо большое количество компиляторов? Я не увидел аргументов дальше в статье. Поддержка большого количества платформ (в том числе старых) — это, безусловно, преимущество, но само по себе разнообразие ничем не хорошо.
Кроме компиляторов С++ есть такое понятие как стандарт
У меня про это целый доклад был: "Санитайзеры и стандарт не спасут":
Компиляторы постоянно нарушают стандарт: разрешают некорректный код, компилируют корректный код неправильно, не поддерживают фичи стандарта, или поддерживают их неправильно. Не говоря уже об расширениях компилятора, которыми можно случайно воспользоваться и не заметить.
Пользователи постоянно нарушают стандарт, обычно случайно. Например, до 2007 года не было никаких проблем с обнаружением переполнения конструкцией
a > a + 100
, но внезапно появились. Стандарт не менялся. Просто компиляторы начали чуть больше оптимизировать.Стандарт довольно сильно ограничен. В C++17 и раньше технически невозможно написать
std::vector
с соблюдением стандарта. По факту все, конечно, пишут, и проблем не возникает. Пока. Посмотрим, как будут вести себя компиляторы через двадцать лет на сегодняшнем коде.А вещественные числа вообще компилятор может трактовать почти как хочет. И в статье, кстати, там всё ещё нет никаких гарантий по стандарту, просто на конкретном компиляторе перестало воспроизводиться.
Недавний пример с работы: одна и та же программа, скомпилированная одним и тем же GCC на одной машине в 32-битном и 64-битном режиме выдавала разные результаты вычислений. При этом всё полностью по стандарту. Просто в одном случае excess precision компилятор добавил, а в другом не добавил.
Есть огромное количество совершенно неожиданного UB: https://github.com/Nekrolm/ubbook
И знаете как мне это удалось? Я просто использовал конкретный стандарт языка, в первом случае С++ 98, во втором скажем так общий стандарт С++ до шаблонов. Ну не прелесть ли? Что вы сейчас думаете о совместимости С++?
Совместимость C++ двадцатипятилетней давности, как вы продемонстрировали, прекрасная. А если на C89 писать, то будет ещё лучше. Но обычно хочется писать хотя бы на C++11, где с совместимостью всё уже далеко не так хорошо.
Кто что подразумевает под словами "C++" — отдельный вопрос.
С++ никогда не ломает обратную совместимость
C++ ломает обратную совместимость в каждом, каждом выпущенном стандарте. Даже есть отдельный раздел "что сломали на этот раз". А если вам примеры оттуда кажутся академическими — так это потому что они минимизированы до предела. В реальном коде каждый такой пример был бы размазан между трёмя файлами и парой сотен строк.
вы просто берете и собираете код компилятором с поддержкой нового стандарта
И у вас старый код перестаёт компилироваться в лучшем случае, а в худшем — просто меняет своё поведение. Я и на работе это иногда вижу, и был про отличный доклад: "Как обновить компилятор и не тронуться". Бонусные баллы, если у вас код не писался исходно с расчётом на совместимость между компиляторами.
У нас не компилится проект, по причине того, что компилятор считает программиста, обезьяной с клавиатурой в руках. Прям как в Rust'е.
Повысить уровень предупреждений можно, но до Rust ещё очень, очень, очень далеко. Снова упрощённый пример с работы:
#include <iostream>
#include <string>
std::string read() { return "foo"; }
int main() {
std::string_view s = read();
std::cout << s << "\n";
}
Компилируется без предупреждений, запускается, выводит foo
. А UB есть. И даже выстрелит, если строчку сделать подлиннее. А если не делать — пройдёт любое тестирование. Конечно, опытный программист на C++ сразу заметит подвох, но мы же статический анализ обсуждаем. Address Sanitizer тоже может помочь, но только если весь код будет скомпилирован сразу с ним, а не размазан между зависимостями, которые задолбаться перекомпилировать.
Кстати, если слишком сильно повысить уровень предупреждений — пойдут false positives, я лично видел это массово у студентов в домашних заданиях, рассказывал, как заглушить, и находил соответствующий баг компилятора.
Не нужно использовать сырые указатели, делать new в каждой сточке, увлекаться арифметикой указателей. А использовать высокоуровневые концепции и надстройки. Это и умные указатели, контейнеры, шаблоны и т.д. Это уже давно есть и работает, нужно просто не делать себе больно и всё.
Это всё не помогает избежать UB. Есть ODR, есть порядок инициализации, есть желание некоторых программистов нарушить strict aliasing "потому что так же эффективнее", есть неявное взятие чистых ссылок и указателей (пример кода выше со string_view
)... И сошлюсь ещё раз на UBBook с бытовыми примерами: https://github.com/Nekrolm/ubbook/
Вам всё равно нужно обрабатывать ошибки.
Разумеется, тут соглашусь. Rust не решает всех проблем. Как и статическая типизация не решает всех проблем, но многие (не все) почему-то предпочитают писать крупные длинные проекты на языках, где опечатка в имени переменной выявляется заранее, а не во время тестирования.
Справедливости ради, анализатор gcc ваш пример сразу поймал:
./1.cpp:3:29: warning: use of uninitialized value ‘<unknown>’ [CWE-457] [-Wanalyzer-use-of-uninitialized-value]
3 | std::string read() { return "foo"; }
| ^~~~~
‘std::string read()’: events 1-3
|
| 3 | std::string read() { return "foo"; }
| | ^~~ ~~~~~
| | | |
| | | (3) use of uninitialized value ‘<unknown>’ here
| | (1) region created on stack here
| | (2) capacity: 8 bytes
|
./1.cpp:3:29: warning: use of uninitialized value ‘<unknown>’ [CWE-457] [-Wanalyzer-use-of-uninitialized-value]
3 | std::string read() { return "foo"; }
| ^~~~~
‘int main()’: events 1-2
|
| 4 | int main() {
| | ^~~~
| | |
| | (1) entry to ‘main’
| 5 | std::string_view s = read();
| | ~~~~~~
| | |
| | (2) calling ‘read’ from ‘main’
|
+--> ‘std::string read()’: events 3-6
|
| 3 | std::string read() { return "foo"; }
| | ~~~ ^~~~ ~~~~~
| | | | |
| | | | (6) use of uninitialized value ‘<unknown>’ here
| | | (3) entry to ‘read’
| | (4) region created on stack here
| | (5) capacity: 8 bytes
|
Я так понимаю, речь про -fanalyzer
. Но он же полную фигню написал. Там нет неинициализированных значений, там просто use-after-free. Ладно, у нас там UB и предположим, что это просто сообщение кривое.
Но так-то стало хуже: если заменить string_view
на string
, то сообщение остаётся, а вот UB полностью уходит:
<source>:3:29: error: use of uninitialized value '<unknown>' [CWE-457] [-Werror=analyzer-use-of-uninitialized-value]
3 | std::string read() { return "foo"; }
| ^~~~~
'int main()': events 1-2
|
| 4 | int main() {
| | ^~~~
| | |
| | (1) entry to 'main'
| 5 | std::string s = read();
| | ~
| | |
| | (2) calling 'read' from 'main'
|
+--> 'std::string read()': events 3-6
|
| 3 | std::string read() { return "foo"; }
| | ~~~ ^~~~ ~~~~~
| | | | |
| | | | (6) use of uninitialized value '<unknown>' here
| | | (3) entry to 'read'
| | (4) region created on stack here
| | (5) capacity: 8 bytes
|
Итого имеем статический анализатор, который выдаёт одинаковое (неверное) сообщение и на корректной программе, и на некорректной. Довольно бесполезно. Можно баг зарепортить.
да, фигню сказал. Ещё и повторил чтобы мы не пропустили))
вероятно про неинициализированную память он говорит потому что там SSO и не происходит аллокаций в хипе для "foo".
Хотя то, что сообщение остаётся с стрингом вместо вью довольно странно.
Сиди гадай что он хотел сказать
Вы полностью правы. И ваш доклад я смотрел несколько раз. Все приведённые доводы из практики. Что сказать, имеем некоторые проблемы:)
Я и правда при профессиональной деятельности на С++, имел дело с разработкой под Linux и другим проектом под Windows. И для каждого проекта использовали только один компилятор поэтому и не сталкивался с описанными вами ошибками.
Правильно понимаю, что бы вылечить С++ от UB, нужно править ядро языка, что повлечёт нарушение обратной совместимости, в том числе и с С?
Правильно понимаю, что бы вылечить С++ от UB, нужно править ядро языка, что повлечёт нарушение обратной совместимости, в том числе и с С?
Не совсем. Если мы каким-то чудом определим всё неопределённое поведение, то это будет полностью обратно совместимо: раньше программа X могла делать что угодно, теперь она обязана делать Y.
Будут технические проблемы из-за того что никто никогда не ставил цели "формально определить поведение". Из-за этого что является корректным разыменованием указателя по стандарту — довольно странная конструкция. Например, может оказаться, что если есть два int*
с одинаковым битовым значением, то один разыменовывать можно, а другой нельзя, потому что они были получены разными способами. Были две прекрасные статьи на тему: раз (перевод на русский) и два (на английском).
Предположим даже, что наука продвинулась достаточно, что формально определить что является UB.
Дальше будет самое страшное — уговорить весь зоопарк компиляторов делать что-то одно. Даже сейчас пропихнуть что-то в стандарт — дело непростое, и поведение компиляторов отличается. А если это что-то настолько радикальное, как потребовать, скажем, при переполнении массива кидать исключение — туши свет. Исключения есть не везде. Если потребуем ронять программу или выводить на экран стектрейс — неясно, что делать на микроконтроллерах. И как-то удовлетворить все группы пользователей C++ лично мне кажется невероятным.
Бывают ситуации ещё сложнее: пусть мы передали в std::sort
некорректный компаратор. Сейчас поведение не определено; реализации могут падать, сортировать как-то, или даже навечно зависать из-за нарушения внутренних инвариантов. Что такое "потребовать обнаружение некорректного компаратора" — большой вопрос. В Java поведение, если не ошибаюсь, тоже не определено, но в гораздо более сильно смысле: программе всё ещё запрещено портить память. А зациклиться или выкинуть случайное исключение или получить мусор в выходном массиве вроде бы можно.
Так что, мне кажется, гораздо проще сделать с нуля язык без UB, хотя бы относящегося к памяти, чем пытаться определить вообще всё поведение в C++. Rust, Carbon, Nim и прочие товарищи чем-то таким и занимаются.
Спасибо за содержательный ответ.
Так что, мне кажется, гораздо проще сделать с нуля язык без UB, хотя бы относящегося к памяти, чем пытаться определить вообще всё поведение в C++. Rust, Carbon, Nim и прочие товарищи чем-то таким и занимаются.
Согласен.
Замечу, что новый язык долго взлетает, да он решает серьезные роблемы, но инфраструктурой и поддержкой или очень долго обрастает Rust или просто забывается как пример D. А софт нужен именно сейчас, потому и берут как пример С++.
Будут технические проблемы из-за того что никто никогда не ставил цели "формально определить поведение".
Цели-то такие может и ставили, но вот достичь их оказалось невозможно: в комитете слишком много участников с несовпадающими интересами. Например, именно поэтому в стандарте C у мьютексов нет возможности проверить, захвачен он текущим потоком или нет. Между разработчиками FreeBSD и кем-то из комитетчиков был феерический срач на эту тему в какой-то рассылке.
Есть же рекомендация: std::string_view передавать в качестве параметра, а возвращать из функции только std::string. Рекомендация примерно того же сорта, как "не используйте сырые указатели". Можно забить на рекомендации и иметь проблемы, а можно им следовать.
Здесь параметров у функции нет, а возвращается std::string
, ваша рекомендация выполняется.
Могла бы спасти рекомендация "следите за всем переменными типа std::string_view
и не записывайте в них временный std::string
при помощи неявной конвертации". Но она не такая известная.
А ещё такой код легко можно получить уже постфактум: сначала функция возвращала, скажем, const char*
, потом её модернизировали в соответствии с вашей рекомендацией. И раньше код работал, а потом перестал.
Уточню рекомендацию: использовать std::string_view только в параметрах функций.
сначала функция возвращала, скажем,
const char*
ИМХО, такие функции надо переписывать, чтобы возвращали std::string. Собственно, "не используйте сырые указатели" подразумевает и "не возвращайте сырые указатели".
Если педантично следовать этой рекомендации, в коде появятся ненужные копирования в std::string. Да и вообще, откуда появятся string_view, если все функции возвращают string.
откуда появятся string_view, если все функции возвращают string
Зато принимают std::string_view
Если педантично следовать этой рекомендации, в коде появятся ненужные копирования
Тем не менее, от того же Полухина слышал рекомендацию: в функцию передавать std::string_view, из функции возвращать std::string. При RVO лишнего копирования не будет. Если я не прав, поправьте меня.
Зато принимают std::string_view
И что? Кто создаст string_view?
При RVO лишнего копирования не будет. Если я не прав, поправьте меня.
Типичный кейс: приняли http-заголовки в буфер, построили из него большой string_view, разбили заголовок на строки в vector<string_view>
, следующая функция разбивает каждую строку на key: value
, принимая string_view
, возвращая pair<string_view, string_view>
. Все string_view построены на памяти исходного буфера, без какого-либо копирования. Чем тут поможет RVO, если функции обработки будут возвращать промежуточные string
Надо писать const auto&
и не думать
Вопрос, куда потом это const auto& передается.
Тут проблема в самом std::min
, можно было бы сделать перегрузку для rvalue ссылок.
Можно как-то так сделать
auto min(auto&& a, auto&& b) -> std::remove_reference_t<decltype(a)>
requires std::is_rvalue_reference_v<decltype(a)> || std::is_rvalue_reference_v<decltype(b)> {
static_assert(std::is_same_v<std::remove_reference_t<decltype(a)>, std::remove_reference_t<decltype(b)>>);
return (a < b) ? std::forward<decltype(a)>(a) : std::forward<decltype(b)>(b);
}
template<typename T>
auto min(const T& a, const T& b) -> const T& {
return (a < b) ? a : b;
}
Или сделать всегда move()
но это очень ограниченное решение.
Тут и возвращается string, только компилятор не предупреждает, что после ; этот string благополучно умрёт, а string_view останется, но будет показывать в никуда.
$ clang++ main.cpp
main.cpp:5:26: warning: object backing the pointer will be destroyed at the end of the full-expression [-Wdangling-gsl]
$ sed 's/_view//' -i main.cpp && clang++ main.cpp
$
???
Про токсичность коммьюнити кстати. Да, разработчики на Rust, включая меня, очень любят его упоминать и рассказывать о его плюсах. Но я не считаю это токсичностью, максимум пиаром, поскольку это распространение технологии и перечисление вещей, которые там действительно есть. Конечно, если человек не умеет нормально общаться - это другой случай, но такое все же гораздо реже происходит. При этом я сижу в куче айтишных чатов с тысячами участников по разным языкам, и не раз наблюдал, как в Rust чат залетают шальные разработчики на C++ и Go и начинают поливать Rust и всех причастных помоями, просто потому что. И с ними пытаются как-то еще адекватно разговаривать. А в C++ чате наоборот, даже без упоминания Rust регулярная грызня между собой, то какая система сборки лучше, то еще что, с попытками обосрать оппонента, а не привести аргументы (при упоминании же Rust там вообще взрыв происходит). И в целом, я бы сказал, что Rust коммьюнити часто с чрезмерным энтузиазмом его продвигает (сам грешен), тогда как C++ коммьюнити по большей части состоит из "да не нужон он нам, этот ваш ынтернет" и элитизма, поливая грязью все "несерьезные" языки и бросаясь с шашкой на любые попытки привнести удобство или безопасность ("это все для криворуких, настоящим программистам нормально и без системы сборки")
Повысить уровень предупреждений можно, но до Rust ещё очень, очень, очень далеко
Я чуть дополню этот пункт, с вашего позволения
Основная разница тут в том, что какими флагами и анализаторами не обмазывайся, если сам язык не дает им необходимой информации, то они не смогут обнаружить ошибку. Пример - лайфтаймы (время жизни) ссылок в Rust. Там они явные. Если они не очевидны компилятору, то их надо явно указывать, как раз чтобы он мог их проверить и гарантировать, что не будет ошибок при обращении. Так же и с боровом (borrow checker)
Вот у меня есть пример небольшой
#include <iostream>
#include <vector>
int main() {
std::vector<int> testVector{0, 1, 2, 3};
const int &vecRef = testVector[0];
for (int i = 4; i < 10000; i++) {
testVector.push_back(i);
}
std::cout << vecRef << std::endl;
return 0;
}
Этот код я собирал gcc и clang на Линуксе, с флагами -W -Wall -Wextra -Werror
. И отдельно проверял вариант без оптимизации и вариант с -O3
. Всего получилось 4 бинарника (по 1 на каждый компилятор и по 1 на каждый вариант оптимизации). Так вот, после запуска каждый бинарник мне выдал абсолютно разное число, и все они были порядка миллиардов.
Этот код собрался без единого предупреждения или ошибки, несмотря на все флаги. И при этом он выдает что попало, вообще не то, что могло задумываться разработчиком. Как я понимаю, это потому что при расширении вектора он переезжает в новую область памяти, побольше, а ссылка начинает указывать на мусор.
Если можно как-то еще повысить уровень обнаружения ошибок, буду рад услышать о нем, все же на С++ я писал последний раз очень давно, мог что-то упустить. Но такую простую вещь надо искать так, что по первым 5 ссылкам в гугле не найти, то это тоже говорит об удобстве разработки на языке.
fn main() {
let mut test_vector: Vec<i32> = vec![0, 1, 2, 3];
let vec_ref: &i32 = &test_vector[0];
for i in 4..10000 {
test_vector.push(i);
}
println!("{}", vec_ref);
}
Я специально сделал чуть более многословно, можно было типы у переменных не указывать, потому что они однозначно выводятся. Но пусть будет так, для исключения непонимания.
Этот код не собирается. Со следующей ошибкой:
error[E0502]: cannot borrow `test_vector` as mutable because it is also borrowed as immutable
--> src\main.rs:6:9
|
3 | let vec_ref: &i32 = &test_vector[0];
| ----------- immutable borrow occurs here
...
6 | test_vector.push(i);
| ^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
...
9 | println!("{}", vec_ref);
| ------- immutable borrow later used here
То есть, язык нам еще на этапе сборки дает по рукам и не позволяет получить мертвую ссылку (в данном случае благодаря борову, но с лайфтаймами в других ситуациях было бы аналогично). Причем, если вырезать ее вывод на экран, то код запустится, потому что она будет неиспользованной, а значит целиком вырежется при сборке. Но как только ее больше нельзя вырезать - она порождает ошибку.
И таких примеров полно. Safe Rust гарантирует невозможность двойного освобождения, дедлока из-за забывчивости отпустить мьютекс после использования (сам дедлок возможен, но по другим причинам), отсутствие обращений по мертвым ссылкам и висячим указателям, невозможность случайного обращения к несинхронизированным данным в многопотоке и много чего еще. И далеко не все из этого списка можно накрутить костылями на другие языки, потому что они просто не дают этим костылям информации для выявления данных ошибок. И да, как уже упоминали, даже unsafe Rust не значит "как в С", там все еще полно проверок, просто они ослаблены
Как сказала одна разработчица на С++, написавшая свой игровой движок с нуля для собственного использования, когда начала трогать Rust:
Как выглядит опасный код на Rust: unsafe fn
Как выглядит опасный код на C++: int main
И это конечно шутка, но в каждой шутке есть доля боли. Особенно если вспомнить замечательные примеры на C++, когда даже однозначно недостижимый кусок кода внезапно выполнялся или когда в gcc критические плавающие баги, ломающие вещественные вычисления на куче платформ, по 30 лет висели без исправления (в исходном комментарии тоже была ссылка на эту статью, но решил чуть подробнее упомянуть)
Конечно, можно отмахнуться стандартным "вы же профессионалы, пишите сразу без ошибок, и все будет хорошо". Что автор статьи и попытался кстати сделать своим "просто тестируйте код". Но мы все же живем в реальном мире и должны знать, что это не работает, никогда. И советы уровня "доктор, я сломал ногу в двух местах - больше не ходите в эти места" не должен применяться, если речь идет хоть о какой-то объективности
Про совершенно другой уровень тулинга, экосистемы и тд уже говорилось. Конечно, можно окуклиться в своем мире, где все на концептах и код пишут только как надо, все на единой системе сборки, есть генерация доки, модули вместо инклудов и прочие пони и радуги. Но рано или поздно придется выйти в реальный мир, а там десять несовместимых систем сборки + еще миллион самописных под каждый проект (я довольно часто слышу от плюсовиков высказывания типа "ни одна система сборки не будет лучше той, что я напишу для себя"), код на дефайнах дефайнов вместо функций целиком и зависимость имеет инструкцию для сборки уровня "надо вот такие 3 библиотеки из 80-х, для которых уже даже исходников нет".
В VCPKG и Conan, двух известных мне пакетниках для плюсов, суммарно на данный момент чуть более 4000 пакетов. На crates.io (глобальный репозиторий Cargo пакетов у Rust) сейчас более 145000 пакетов доступно. Это разница уже не в разы, это на порядки. Конечно, тоже частый аргумент - якобы это все просто мусором завалено, реальных библиотек там единицы. Но это можно сказать вообще про любой глобальный публичный репозиторий любого языка, и это не мешает там появляться сотням и тысячам очень крутых проектов, которые никогда бы не родились, если бы надо было пройти через огонь, воду и медные трубы ради публикации своего детища. Все хорошие экосистемы типа Java живут на доступности и простоте, а не на десяти замках
Про embedded тоже можно сказать, что по-умолчанию, если не отключать ничего, то Rust отслеживает вылеты за границы массива и самоубивается до того, как реально полезет в память и попытается ее попортить. И на микроконтроллерах без защиты памяти это вполне может спасти многое.
И в целом, Rust - это про безопасность "по-умолчанию". Можно сделать гадости, как на любом языке. Но они будут сделаны намеренно. Зато не случится проблемы, если ты что-то забыл, не заметил и тд. Самый дефолтный вариант - самый стабильный и безопасный. Вот если хочешь странного, тогда да, тогда уже надо прикладывать усилия, таким образом думать, таким образом исключая многие возможности нагадить случайно.
Можно еще про стандартную библиотеку упомянуть, где у C++ нет ни базовых вещей типа сплита строки, ни нормальной работы с сетью даже (когда Wi-Fi сейчас даже в самых дешевых микроконтроллерах есть), а Rust ее очень активно развивает и тащит в том числе и наработки Google по C++ (SwissTable как std реализация HashMap, например), можно еще много что сказать. Но это уже по сути отдельный холивар будет, я и так увлекся
В общем, я очень давно ушел с С++ на Java, а примерно года полтора назад стал параллельно с основной работой на высоконагруженном Java бэке осваивать Rust и применять его в домашних проектах (и бэкенд, и домашний embedded проект). И это прям вернуло мне веру в то, что на низкоуровневых языках и в железной и системной разработке можно не страдать. Начал вот embedded проект домашний, начал GAPI голые осваивать, системные штуки писать. Раньше думал, что это все было больно, потому что сферы такие. А оказалось, что просто язык не тот был. И многие старые разработчики на C++ это тоже понимают и уходят (даже знакомый сишник/плюсист с 35 годами стажа, начинавший еще на голом асме и писавший все это время критические системы, пусть сначала и поплевался с непривычки, но теперь втянулся и постоянно возмущается, почему не могли ту или иную удобную штуку в плюсы утащить). А многие воспринимают это в штыки, ибо как так, наконец появился конкурент, надо срочно обосрать. У нас в СНГ это еще заметнее, ибо пока на Западе огромные именитые проекты перекатывают на Rust с плюсов и голенга и вбухивают миллиарды в новые на нём, у нас до сих пор массово менталитет "да не нужон этот ваш ынтернет"
Основная разница тут в том, что какими флагами и анализаторами не обмазывайся, если сам язык не дает им необходимой информации, то они не смогут обнаружить ошибку.
Это довольно сильное заявление. В алгоритмическую неразрешимость задачи останова в общем случае я верю, теорема Райса тоже верна, а в каждом конкретном случае всегда можно написать какое-нибудь дополнительное предупреждение, которое подсветит проблему. Например, сторонний инструментcppcheck
прекрасно видит проблему без каких-либо дополнительных флагов:
Checking a.cpp ...
a.cpp:12:18: error: Reference to testVector that may be invalid. [invalidContainerReference]
std::cout << vecRef << std::endl;
^
a.cpp:6:23: note: Assigned to reference.
const int &vecRef = testVector[0];
^
a.cpp:8:23: note: Assuming condition is true.
for (int i = 4; i < 10000; i++) {
^
a.cpp:8:23: note: Assuming condition is true.
for (int i = 4; i < 10000; i++) {
^
a.cpp:9:20: note: After calling 'push_back', iterators or references to the container's data may be invalid .
testVector.push_back(i);
^
a.cpp:12:18: note: Reference to testVector that may be invalid.
std::cout << vecRef << std::endl;
Ну а если заниматься ещё и динамическим анализом вроде Address Sanitizer/Valgrind, то найдётся абсолютно всё, тут теорема Райса неприменима.
Не то чтобы я не согласен, но это скорее вопрос веры: считаем ли мы класс ошибок X настолько критичным, чтобы заставлять программиста доказывать их отсутствие в программе компилятору. С учётом того что и в Rust бывают баги и в компиляторе, и в borrow checker, и в стандартной библиотеке.
то это тоже говорит об удобстве разработки на языке
Вы считаете, что удобство разработки — это отсутствие UB. Опять же, не то чтобы я не согласен, но есть и другая точка зрения: удобство разработки — это когда язык не мешается под ногами. Например, была тут серия статей про разработку игр на Rust — там хочется писать много-много некорректного кода для прототипирования, а не рефакторить, чтобы удовлетворить компилятор. К баттлу C++ vs Python, кстати, тоже относится: много кому нравится именно Python за возможность быстро нафигачить кода и не думать толком про типы данных и времена жизни. Кстати, по этой же причине Python может нравится и больше Rust.
Safe Rust гарантирует невозможность
Если нет багов в unsafe-библиотеках, компиляторе, borrow checker... Что определённо хорошо и я доволен, но не всех убеждает. Мол, вопрос, рисков.
И да, как уже упоминали, даже unsafe Rust не значит "как в С", там все еще полно проверок, просто они ослаблены
Можете пояснить? Кажется, что там полностью убраны проверки при разыменовании указателей, например. А это самое страшное.
Что автор статьи и попытался кстати сделать своим "просто тестируйте код". Но мы все же живем в реальном мире и должны знать, что это не работает, никогда.
Опять же, вопрос веры.
Конечно, можно окуклиться в своем мире, где все на концептах и код пишут только как надо, все на единой системе сборки, есть генерация доки, модули вместо инклудов и прочие пони и радуги. Но рано или поздно придется выйти в реальный мир
В некоторых компаниях ровно так и происходит, никакого "реального мира". И платят хорошо. Довольно удобно в них работать было, кстати, никакой Rust и близко не подобрался по уровню тулинга, который там был для C++.
Можно сделать гадости, как на любом языке. Но они будут сделаны намеренно.
Буквально позавчера видел баг на Rust, когда кодом деньги списывались, а нужное действие не совершалось. Сделано было случайно.
И это прям вернуло мне веру в то, что на низкоуровневых языках и в железной и системной разработке можно не страдать.
Можно. А много кто и не страдает! В этом отличие. Люди пишут на Си и C++ с удовольствием и не парятся про какое-то там UB, несоответствие стандарту и прочая, прочая, прочая. Вот тут-то стычка и происходит.
И многие старые разработчики на C++ это тоже понимают и уходят
Да и c Си тоже, вот пример доклада, как контрибьюторы Postgres начали Rust использовать в своём проекте: https://www.youtube.com/watch?v=kAQeout-mh8
А многие воспринимают это в штыки, ибо как так, наконец появился конкурент, надо срочно обосрать.
Не поэтому, а потому что Rust ставит программисту очень много ограничений ради той самой безопасности. Нужны ли эти ограничения — вот тут расхождение мнений.
Например, сторонний инструмент
cppcheck
прекрасно видит проблему без каких-либо дополнительных флагов:
А многие сидят без чекеров или с более тупыми чекерами. Опять же, частный случай, когда используется только то, что нужно - это именно частный случай
И это только данный пример с боровом. Как подобный инструмент будет валидировать время жизни ссылок в сложных случаях вызовов библиотек с кучей фукнций и тд? В Rust я знаю, что там будут лайфтаймы ссылок, по которым уже можно однозначно проверить их время жизни (для чего и были придуманы). А если в языке просто нет этой однозначности?
Ну а если заниматься ещё и динамическим анализом вроде Address Sanitizer/Valgrind, то найдётся абсолютно всё, тут теорема Райса неприменима.
Откуда тогда такое количество глупых багов и уязвимостей-то берется? Видимо далеко не все используют. Ну и опять же, это теплое с мягким, потому что я говорю про проверки времени сборки, а это инструменты для анализа рантайма. Далеко не каждый проект можно тривиально гонять в рантайме по всем возможным случаям, плюс это тоже доп работа и тд. Против того, что гарантирует сам язык
считаем ли мы класс ошибок X настолько критичным, чтобы заставлять программиста доказывать их отсутствие в программе компилятору. С учётом того что и в Rust бывают баги и в компиляторе, и в borrow checker, и в стандартной библиотеке.
99% защита от ошибок все еще лучше, чем 10%. Цифры, очевидно, случайные, но суть думаю ясна. Было исследование университета Ватерлоо по похожему поводу. Исследовали вероятность занесения уязвимости (простые баги не учитывались) новым коммитом в проект на C++ и Rust, в зависимости от опыта разработчиков. В итоге вышло 4% для C++ и 0.06% для Rust в сложном проекте. При этом было примечание, что в проектах на C++ многие уязвимости вносились повторно, уже после их устранения, по мере развития проекта. И 87% уязвимостей в C++ относились к ошибкам памяти, которые как раз и предотвращает Rust. Похожая статистика была и у Microsoft, там было 70% их уязвимостей из-за памяти, и они тоже ссылались на Rust как на решение
там хочется писать много-много некорректного кода для прототипирования, а не рефакторить, чтобы удовлетворить компилятор
Ну это справедливо ровно в той же степени, в какой "в TypeScript хочется писать везде any и не думать о типах". То есть, да, для кого-то это так. Но тогда не было смысла брать такой язык, если не хочешь пользоваться его преимуществами
Кстати, по этой же причине Python может нравится и больше Rust.
Это разные языки для разного класса задач. Никто вроде и не предлагал заменять Python на Rust, это, чаще всего, не имеет смысла. К слову, питонисты гораздо лояльнее плюсистов относятся к Rust, для них это возможность писать критические по производительности и безопасности секции не на C++. Как пример - pydantic, ruff и другие тулзы и либы для питона написаны на Rust под капотом, равно как огромное количество либ до этого писали на C++. Для этого в Rust есть библиотека PyO3, обеспечивающая двустороннее взаимодействие с питоном. И я нередко слышал от питонистов, что им Rust был гораздо проще C++ при освоении для такого круга задач (написания библиотек под питон), отчасти благодаря удобству тулинга, отчасти как раз благодаря надежности, знанию, что ты вот попарился с кодом, зато потом он заработает и не придется погружаться в ад UB и приколов конкретного плюсового компилятора
Ну и я не считаю, что разница Rust vs C++ настолько кардинальна, как Rust vs Python или C++ vs Python. В первом случае это, можно сказать, прямые конкуренты, во втором это языки совсем разного уровня
Можете пояснить? Кажется, что там полностью убраны проверки при разыменовании указателей, например. А это самое страшное.
unsafe Rust дает только следующие послабления:
Разыменование голого указателя
Вызов другого unsafe кода
Доступ к мутабельным статик переменным
Реализация unsafe трейта (аналог интерфейсов из Java, C# и других языков)
Доступ к полям union
Borrow checker, проверка жизни ссылок, RAII повсеместное и остальные проверки языка все остаются на месте. unsafe создан для обращения к железу, к библиотекам на других языках (которые по определению unsafe, поскольку Rust не может их проверить), к API операционных систем и подобным внешним вещам, безопасность которых мы не можем гарантировать
В некоторых компаниях ровно так и происходит, никакого "реального мира". И платят хорошо.
Опять же, частный случай. Сегодня у вас все самописное и в шоколаде, а завтра надо тащить зависимость с нечитаемыми исходниками, утыканными UB, которую не сбилдить просто так современным компилятором и тд. Или всё-всё писать с нуля под себя, до каждой библиотеки? А что будет, если человек уволится и уйдет в другую компанию, где все не так? Единичные позитивные случаи не исключают общей проблемы
Возьмем пример Unreal Engine. Огромный проект на C и C++, почти 80 миллионов строк очень сложного кода, поддержка проприетарных платформ (консолей) и тд. И что в итоге? Пришлось делать свою систему сборки, полностью выбросить STL (как они написали, из-за проблем со стабильностью и кроссплатформерностью), пилить свою рефлексию и макросы, и вообще по сути сделать Unreal C++. И при билде движка скачивается несколько десятков гигабайт зависимостей, которые, я уверен, далеко не все следуют этим вот "правилам идеального мира"
Буквально позавчера видел баг на Rust, когда кодом деньги списывались, а нужное действие не совершалось. Сделано было случайно.
Ни один язык не предотвращает баги полностью, поскольку ни один язык не может валидировать алгоритмы в отношении бизнес-логики. Я говорю же про конкретные проблемы и классы ошибок, которые в Rust просто невозможны. Например, дедлоки и утечки памяти возможны даже в safe Rust, от этого тоже ни один язык не спасает (если вообще поддерживает многопоток). Но Rust предотвращает многие причины их возникновения, пусть и не все. И вот это уже и важно, и ценно
Люди пишут на Си и C++ с удовольствием и не парятся про какое-то там UB, несоответствие стандарту и прочая, прочая, прочая
А потом набирают вот такую статистику по уязвимостям, получают плавающие баги и прочие радости. Опять же, можно закрыть глаза на проблемы, сочтя, что слишком муторно писать так, чтобы их гарантированно не было (см. Typescript vs Javascript). Но мы вроде не об этом говорили
И таки я уже второй раз слышу, что тулинг под плюсы может быть шикарным, лучше Rust, и писать там можно с удовольствием. Допустим, что там где-то за закрытыми дверьми у кого-то сделано удобно. Но это не значит, что у всех так хорошо. Часть тулинга Rust просто не имеет популярных аналогов, часть имеет, но они вообще не общеприняты. Да и кроме тулинга, это просто чудесное сочетание полупустой стандартной библиотеки и жуткой боли при использовании сторонних библиотек.
Я когда начал изучать GAPI, штудировал многие книги на плюсах, потому как по Rust, ввиду новизны, информации по такой нишевой сфере было мало. И я просто регулярно ужасался, как для абсолютно идентичного результата плюсовики вынуждены мучаться на ровном месте, потому что то либ нет, то они с махровым сишным апи, вокруг которого надо танцевать, то билдить с бубном и танцами у костра нужно...
как контрибьюторы Postgres начали Rust использовать в своём проекте
Очень многие начали. Cloudflare целиком на Rust переписались, в исходниках Android его уже столько же, сколько C, AWS несколько сервисов целиком переписали и не останавливаются (и сделали Firecracker целиком на нем, опенсорс менеджер микровиртуалок, на котором работают Lambda и Fargate), с голенга многие тоже переписываются. Я могу дать полный список известных мне проектов, там от MESA до Figma, Vercel и части бэка npm.js репозитория. И под спутники пишут на нем, и новые проекты от мала до велика (от Tauri, который по сути Electron здорового человека, и Embassy, который embedded просто и быстро, до Western Digital, которые платформу обработки данных на 5 петабайт в день на нем пилят). Но возможности спрятать под спойлер я тут не вижу, а еще больше засорять коммент не хочется.
Не поэтому, а потому что Rust ставит программисту очень много ограничений ради той самой безопасности. Нужны ли эти ограничения — вот тут расхождение мнений.
Это говорит обычно здравое меньшинство. И я согласен, что тут есть вкусовщина. Но большинство просто плюется слюнями и ненавидит язык за сам факт его существования. В первую очередь плюсисты и гошники, ибо Rust в основном с ними конкурирует (с первыми в системщине и высокопроизводительных программах, со вторыми - в простом и высокопроизводительном бэкенде, который еще и намного быстрее гошного).
Тут еще играет роль то, что Rust позволяет не спускаться до совсем низкого уровня (пусть он конечно и сам по себе ниже большинства языков). В языке очень много так называемых zero cost abstractions, которые можно использовать как сахар, не боясь отстрелить себе лишние миллисекунды. Даже в embedded разработке на Rust легко можно увидеть дженерики, трейты и их реализации, операции над итераторами (аналог Stream API из Java), и прочие атрибуты высокоуровневой разработки, которые обычно у железа не встретишь вообще (отчасти потому что под железки часто кастом компиляторы си или плюсов, которые многое тупо не умеют)
Upd: опечатки
99% защита от ошибок все еще лучше, чем 10%.
А 0% думания про время жизни (C++) лучше, чем 2%. Потому что меньше мозг нагружается. И что? Вы всё ещё отталкиваетесь от мысли, что надо минимизировать количество ошибок управления памятью. Это де-факто не всегда так, бывают и другие приоритеты.
Если скажете, что, мол, надо смотреть на долгосрочное думание про время жизни и всё равно всплывёт, то я скажу, что баги можно и не фиксить, будет автоматически перезапускаться раз в полчаса и никаких проблем.
Опять же, частный случай. Сегодня у вас все самописное и в шоколаде, а завтра надо тащить зависимость с нечитаемыми исходниками, утыканными UB, которую не сбилдить просто так современным компилятором и тд.
Всё есть частный случай, вообще каждое решение принимается в частном случае. Когда людям нужно выбрать язык — они выбирают язык под свой частный случай. Статистику можно подогнать любую.
А так у меня речь шла про компанию уровня FAANG с офисами в десятках стран мира и десятками (если уже не сотнями) терабайт исходников. Куда уж "реалистичнее". Они что-то, наверное, про сборку большого количества кода с зависимостями понимают. Их решения не всем подойдут, мягко скажем, но работает отлично в рамках компании.
Но Rust предотвращает многие причины их возникновения, пусть и не все. И вот это уже и важно, и ценно
Вам ценно. Мне тоже нравится. Но не всем ценно.
И я просто регулярно ужасался, как для абсолютно идентичного результата плюсовики вынуждены мучаться на ровном месте
То же самое могу сказать и про Rust: пробуем эффективно реализовать какую-нибудь нетривиальную структуру данных (тот же интрузивный список) — либо не получается, либо всплывает неэффективный Rc
, либо пишем unsafe
. Если вам на это хочется ответить, что, мол, надо использовать правильные абстракции и не писать интрузивные списки самостоятельно или изолировать их в отдельных крейтах или вся суть в том чтобы помечать такой код unsafe
— то вы, кажется, не поняли претензию.
Вы смотрите на то, как люди пишут на C++, и ужасаетесь, что они это делают не так, как на Rust, и тратят время на другие проблемы. А они смотрят на то, на что они будут тратить время при написании кода на Rust и думают то же самое.
В языке очень много так называемых zero cost abstractions
А такие уж они zero cost? Вон в C++ внезапно оказалось, что std::unique_ptr
ни разу не zero-cost, хотя казалось бы: обёртка над чистым указателем, которая вызывает delete
в деструкторе. Ан нет — ABI мешается. Не стандарт, именно ABI. И, подозреваю, у Rust с ABI должны быть похожие ограничения.
Если скажете, что, мол, надо смотреть на долгосрочное думание про время жизни и всё равно всплывёт, то я скажу, что баги можно и не фиксить, будет автоматически перезапускаться раз в полчаса и никаких проблем.
Ошибка защиты памяти - не равно утечка памяти. Ошибка защиты может происходить когда угодно (а не накапливаться со временем), в зависимости от логики программы, и представлять собой критическую уязвимость безопасности. И от регулярных перезагрузок магически никуда не исчезнет
Upd: еще дополню немного. Конечно всегда можно, условно, забить, сделать тяп-ляп и в прод поскорее, для такого Rust будет мешать, это вообще язык не про прототипирование. Но это тоже чревато, вон Cloudflare разок напоролись на Cloudbleed, принесший кучу убытков, как финансовых, так и репутационных, как раз из-за ошибки памяти. И этого им хватило, чтобы начать массовую миграцию на Rust, с написанием своего решения на замену nginx с обвязкой из си и плюсов, которые у них до этого работали. И кстати, в мире Rust давно уже есть rustls библиотека, полный аналог openssl, но на расте (что и позволило проектам на нем избежать Heartbleed в том числе). Пока жареный петух не клюнет, многие не думают. Но те, кто более дальновидный, решают проблемы заранее
Статистику можно подогнать любую.
А так у меня речь шла про компанию уровня FAANG с офисами в десятках стран мира и десятками (если уже не сотнями) терабайт исходников. Куда уж "реалистичнее". Они что-то, наверное, про сборку большого количества кода с зависимостями понимают. Их решения не всем подойдут, мягко скажем, но работает отлично в рамках компании.
Ну вот Microsoft - это не компания уровня FAANG (или теперь их правильно называть MANGA?)? Или они у себя не смогли в тулинг и поэтому столько уязвимостей? Я понимаю, что иногда хочется отмахнуться от неудобной статистики, но с фактами сложно спорить. И это кстати как раз те компании, которые могут себе позволить написать вообще все свое с нуля, благодаря чему и не столкнуться с проблемами простых смертных)
Вы смотрите на то, как люди пишут на C++, и ужасаетесь, что они это делают не так, как на Rust, и тратят время на другие проблемы. А они смотрят на то, на что они будут тратить время при написании кода на Rust и думают то же самое.
Все есть яд и все есть лекарство. Разница только в дозе. Одно дело, когда сложно писать один конкретный тип вещей (для которых и так уже библиотеки написаны. Не забываем, это не плюсы, тут можно не писать все с нуля каждый раз). И другое - когда у тебя весь язык в этих проблемах, от инклудов и зависимостей до совместимости и стандартной библиотеки
А такие уж они zero cost? Вон в C++ внезапно оказалось, что
std::unique_ptr
ни разу не zero-cost, хотя казалось бы: обёртка над чистым указателем, которая вызываетdelete
в деструкторе. Ан нет — ABI мешается. Не стандарт, именно ABI. И, подозреваю, у Rust с ABI должны быть похожие ограничения.
В Rust нет стабильного ABI, частично из-за этого как раз (емнип кстати в C++ тоже нет между компиляторами). Есть только режим сборки в сишную либу и поддержка C ABI, для FFI и подобного. И таки да, есть оптимизации с условной пометкой "может быть", такие как вызов .clone()
, который часто оптимизируется в noop, а есть гарантированные оптимизации (например, что Result и Option занимают ровно столько памяти, сколько голые лежащие в них данные, а сами по себе вырезаются на этапе сборки). Благодаря таким гарантиям можно использовать большую часть сахара и не бояться, что что-то просядет.
например, что Result и Option занимают ровно столько памяти, сколько голые лежащие в них данные
Насколько я помню они не вырезаются никуда, это обычные енумы с дискриминантом Как ж их вырезать, если надо для внешних данных понять, они ок или нет. Для Option
есть оптимизация, которая позволяет ему занимать столько же места, сколько и ссылка т.к. ссылка не может быть null
, но чего-то большего я не помню.
Я имел в виду, что там не происходит какого-то выделения памяти под сами эти "объекты", их вообще по сути нет, есть проверки на их значение (которые тоже там во всякое разворачиваются, например в проверку ссылки), но по сути это те данные, которые в них хранишь. То есть, Option<i32>
займет ровно 4 байта на i32
внутри него, ничего лишнего там не будет, никакого оверхеда относительно голого указателя и ручных его проверок везде. Несмотря на удобство работы с ним в коде, использование дженерика, общий вид как объекта с методами и тд
Это ключевое отличие от аналогов в других языках, например Optional
в Java, который именно отдельный объект со всеми вытекающими (его создание и удаление, оверхед любого объекта в памяти из-за метаданных и тд)
Upd: единственный известный мне язык кроме Rust, где есть такие енамы - это Swift. Они в целом с Rust очень сильно похожи, после Rust я книжку по Swift проглотил за день и пошел писать на нем, когда разок понадобилось, потому что очень сильно смахивает на Rust и Kotlin. И это касается многого, например guard
из свифта - это let else
из раста. Там целый список можно набрать
Ну и бесплатные абстракции, конечно, на енамах не заканчиваются, там вообще очень многое из удобного во время сборки разворачивается в то, как это пишут сишники
Не хочется вас расстраивать, но это не так. Есть там и объект (сами объекты, конечно во время компиляции удаляются и остаются только внутренности), и дискриминант никуда не девается. Есть много крутых штук, вроде хранения дискриминанта в паддинге (и Option<bool>
занимает столько же места, как и bool
), но для обычного i32
впихнуть его некуда. Rust, конечно, крутой, но такое просто невозможно сделать безопасно. Для енумов без дискриминанта (и лишнего места) есть union, но его использование - unsafe.
Про union знаю, чуть выше как раз в моменте про unsafe его упоминал. Про прикол с енамом не знал. Помнил, что на String
и прочих подобных типах всегда выходило одинаково, поэтому отложилось так в голове. Почитаю потом подробнее
К слову, в GAPI, с которыми работаю, очень важно положение данных в памяти, иначе при передаче с CPU на GPU в шейдер мусор приедет вместо данных. Там как раз приходится приседать с паддингами, выравниванием и прочим. Но для wgpu (раст реализации стандарта WebGPU с поддержкой нативных платформ кроме веба, которую использует Mozilla у себя) есть такой вот замечательный проектик, который гарантирует корректное расположение в памяти для произвольных типов данных, причем еще во время сборки. Благодаря чему и получается не думать о лейауте данных в памяти, и такие вот нюансы выветриваются за ненадобностью
И, подозреваю, у Rust с ABI должны быть похожие ограничения.
Rust ABI умышленно unspecified - в том числе как раз чтобы подобные вещи исправлять по мере их появления.
Ну а если заниматься ещё и динамическим анализом вроде Address Sanitizer/Valgrind, то найдётся абсолютно всё
Было бы хорошо, но увы, это не так. Насколько я понимаю разницу, в C++ флаги, санитайзеры и прочее выводят ошибки для кода, который они считают неправильным, в Rust же компилятор не компилирует программу если не может доказать её корректность. И это принципиально разный подход.
Если нет багов в unsafe-библиотеках, компиляторе, borrow checker...
А давайте без этого, в вышеупомянутом видео достаточно примеров багов C и C++ компиляторов, санитайзеров и прочего. Если так рассуждать, то и Python сразу unsafe, ведь в его реализации тоже баги могут быть. С таким подходом лучше сразу на кладбище ползти, там уже не до багов в коде будет. И довольно большая часть Rust'а так вообще формально верифицирована.
там полностью убраны проверки при разыменовании указателей
Нельзя убрать то, чего нет. Разыменование указателей это всегда unsafe операция, для safe кода гораздо чаще используются ссылки.
вопрос веры
Это не вопрос веры, это вопрос данных. И в Rust новые контрибьютеры делают в 70 раз меньше ошибок, чем в C++. Не на 70%, в 70 раз. Какая тут вера.
Буквально позавчера видел баг на Rust
А я сегодня видел пачку багов на C++, и что?
Люди пишут на Си и C++ с удовольствием и не парятся про какое-то там UB, несоответствие стандарту и прочая, прочая, прочая
Только тогда возникает проблема, эти люди - не разработчкики на C++. Код с UB - не программа, а называть программистом людей, которые не пишут программы, а код, мне кажется странным. Это как за количество символов в коде платить.
И это принципиально разный подход.
Согласен, понимаете всё верно, да. А доклад мой, в чём, видимо и ирония :)
Найдётся все теоретически. На практике, конечно, фигня.
Это не вопрос веры, это вопрос данных
Данные есть. Что с ними дальше делать — вопрос веры. Писать ли на Rust или C++, так ли уж важно избавляться от ошибок работы с памятью в конкретных программах, так ли уж важно избавляться от UB...
Код с UB - не программа,
Вы прямо сейчас наверняка отвечали мне в браузере, написанном на C++, содержащем кучу UB. Не программа?
А доклад мой, в чём, видимо и ирония :)
Тогда может я чего-то не понял, но как-то странно в одном месте говорить, что все не найдется, а в другом, что найдется. Это немного путает, особенно людей, кто прочитают этот комментарий в отрыве от всего остального.
Кстати, отличный доклад, с удовольствием посмотрел. Повезло еще, что его в видео выложили вот буквально недавно.
Вы прямо сейчас наверняка отвечали мне в браузере, написанном на C++, содержащем кучу UB
Технически это совсем не обязательно, 1 post запрос можно было бы и из Rust отправить (естественно я этого не делал).
Не программа?
Почему бы и нет. Раз уж стандарт C++ так определяет, то пусть так и будет. Я же не заказчик кода, которому нужен какой-то результат, а программист. А то, что что-то не является программой не значит, что это что-то бесполезное, велосипед тоже не программа, а штука очень крутая и полезная.
Позвольте мне высказаться. Мой опыт писания в сях и русте равен нулю. Кажется опять спор свелся к тому что приходится полагаться на ответственность программиста. А где она? Да какая может быть ответственность когда и так ни черта не работает, прогер не спит нормально и может наделать ошибок из-за бессонницы. Его, что, ответственность волнует? А что если цейтнот? Опять будем полагаться на ответственность? Может пора выкинуть из языка опасные функции?
Выкинуть мешает обратная совместимость. Потому и пишут новые языки.
Может пора выкинуть из языка опасные функции?
Ага. Например, из языка C надо выкинуть операцию сложения. А то из-за неё случаются переполнения, и одни безответственные программисты используют x + 1 < x
для проверки, а у других несчастных это x + 1
при переполнении вызывает trap (на MIPS, например), и проверять уже поздно.
С вычитанием, кстати, то же самое. Тоже надо выкинуть.
Безответственному программисту не поможет никакой язык.
Например, из языка C надо выкинуть операцию сложения
Можно же "просто" (понятное дело не в C, а вообще) определить переполнение и не использовать то, что переопределение не определено (т.к. оно определено) в оптимизациях. И все, проблемы (не все проблемы вселенной, а UB) заканчивается и жить становится сильно проще.
Простейшая и ежедневно имеющая место ситуация нашей действительности int i + 1
: в архитектуре x86 целочисленное (знаковое) переполнение приводит к wrap around, а оно же на MIPS — trap.
Доставляет “приколов” и то, что на этом же самом MIPS целочисленное знаковое переполнение при операциях умножения или деления уже не трапается, как при сложении/вычитании, а молча заворачивается (wrap around).
Так какие ваши доказательства предложения по “определению переполнения” в том же С (или вообще) для упрощения жизни? Как заставить оптимизатор не считать “раз тут так складывают, то переполнения точно не будет”, и получить эффективный код на x86 и на MIPS?
Другой пример: int x / int y
. При y == 0
на x86 — trap, а на PowerPC — спокойно “едем” дальше: Как определить деление на 0 так, чтобы оптимизаторы перестали считать “раз тут делят, то y
точно не 0, и можно слегка подоптимизировать”, и получить эффективный код на x86 и на PowerPC?
С дереференсом 0 тоже полно приколов, Как определить Null pointer dereference так, чтобы оптимизаторы перестали считать “раз тут ptr
дереференсится, то он точно не Null,
и можно наоптимизировать”, и получить эффективный код на x86 (где вообще-то нет никаких проблем отмапить 0 по своей нужде) и на ARM (где обычно на 0 — обработчики исключений)?
Может, проще оптимизаторы запретить? Или собрать специальную рабочую группу ООН, которая напишет, а ГА ООН примет соответствующую резолюцию о единственно расово верных реакциях на все ситуации для всех производителей процессоров? <сарказм>
Словом, «И нашим, и вашим» — не получится, если мы о ЯВУ, у которых “высота” уровня низка настолько, что позволяет легко и эффективно писать программы для bare metal, а не каких-то абстрактных виртуальных машин. У знающих и понимающих целевую архитектуру программистов на ассемблерах описанные выше проблемы не существуют. Правда, у них и чрезмерно прытких оптимизаторов тоже нет. Если разработчик не знает/не понимает архитектуру целевой системы, вряд ли ему следует лезть в C или Rust. В Java или Python ему будет много комфортнее.
А жопоголовому кодеру, который не желает в валидацию данных, не поможет ничего. Он скорее будет возмущаться, что разработчики glibc/bsd libc/musl — идиоты, поскольку в strlen
не проверяют (и правильно делают) параметр на NULL
, а его программа из-за этого трапнулась.
Я же не говорил, что надо выбрать какой-либо вариант и сделать его единственно возможным и все. Я говорил только о том, чтобы сделать поведение определенным, чтобы оптимизатор не мог по велению левой пятки выкидывать (или не выкидывать, или еще что угодно делать) код проверок на переполнение. И из-за этого жизнь становится проще, не надо отключать оптимизации из-за боязни того, что где-то что-то может как-то не так заработать. Просто для другого поведения можно использовать не +
, а метод или функцию, если это необходимо.
Как определить Null pointer dereference
Воспользоваться системой типов для того, чтобы такое отлавливалось бы компилятором.
Словом, «И нашим, и вашим» — не получится
Вариант «ну что-то произойдет, может быть то, что тебе надо» точно работает очень плохо.
вряд ли ему следует лезть в C или Rust
В Rust то как раз очень даже можно, ни одной из вышеперечисленных проблем в Rust нет. Т.е. да, можно получить неожиданное переполнение и программа будет работать неправильно, но это дырой неопределенного размера не будет.
Воспользоваться системой типов для того, чтобы такое отлавливалось бы компилятором.
В том же Rust это отлавливается только в safe-коде. Да, подавляющему большинству разработчиков большего и не надо, но полностью исключить из языка вопрос "что делать, если программа пытается разыменовать нулевой указатель?" здесь, как видно, не удалось.
Только если вы хотите писать код в стиле С на голых указателях.
Для типов Option, NonNull, и т.д. никакие проверки даже в unsafe не вуключаются. И абсолютное большинство библиотек не пишут на голых указателях даже при обертывании C API.
Вообще это распространенный предрассудок, что unsafe выключает все проверки. На самом деле он выключает очень маленький набор проверок, например borrow checker работает.
например borrow checker работает.
А как тогда организовать работу, например, со страницами данных в shared memory? Там же владение страницами регулируется семафорами, внешними для Rust. Ведь доступ к shared memory может выполнятся из любых языков программирования.
А что вам, собственно, мешает?
Я совсем недавно столкнулся с Rust. Как указателями гулять по страницам я понимаю, но что делать с семафорами - уже нет.
P.S. Мерзко и непрофессионально минусовать вопрос новичка.
Использовать типы Rust, которые имеют такой же макет памяти, как и указатели. Например Option<NonNull<T>> это тоже что и *mut T, но вам компилятор не позволит просто разименовать нулевой указатель.
Почитайте https://doc.rust-lang.org/nomicon/ffi.html, но вообще FFI в любом языке это сложная тема. Лучше всего использовать готовую библиотеку, в которой всё уже аккуратно написано. Например https://docs.rs/shared_memory/0.12.4/shared_memory/.
P.S. Минусовал не я.
Лучше всего использовать готовую библиотеку
Проблема в том, что у меня нет уверенности, что готовая библиотека окажется совместима с C/С++ кодом уже работающим с этой shared memory. Особенно в случае, если семафоры страниц организованы через atomic_*().
Ниже я уже написал, что нашел хотя бы, что core::sync::atomic соответствует стандарту C++20.
В библиотеке написано `A thin wrapper around shared memory system calls`. Если ваш с++ код также написан через системные вызовы, то должно быть всё совместимо.
Если же у вас своя реализация семафоров на `atomic_*()`, то надо искать совместимую с ней реализацию Rust через `core::sync::atomic`, и её возможно нет.
Если ваш с++ код также написан через системные вызовы
Во-первых не мой код, а код PostgreSQL. Во-вторых, естественно, не через системные вызовы, так как это слишком медленно, а через atomic.
надо искать совместимую с ней реализацию Rust через
core::sync::atomic
, и её возможно нет
То что её нет как раз не пугает. Вопрос, как написать это самому, чтобы сократить издержки на поддержку среды другого языка при его частых вызовах. В противном случае, окажется бессмысленно писать расширение на Rust и продолжу писать его на C.
Попробуйте взять готовую реализацию семафоров на С или С++ (возможно прямо из PostgreSQL), тогда проблема совместимости семафоров точно отпадает.
Дальше пишете небольшую обертку на Rust, и используете из библиотеки `shared_memory` только сами функции для расшаривания памяти, но не местные семафоры.
В семафорах уже должны быть атомарные операции с правильными порядком доступа к памяти (Acquire/Release/Seqcst), так что разбираться с этим не придется. Но важно в обертке задействовать RAII, чтобы при окончании работы с указателем автоматически вызывалась функция семафора.
Я не знаю, как именно вы хотите работать с семафорами, но для Mutex в Rust принято создавать структуру https://doc.rust-lang.org/std/sync/struct.MutexGuard.html, в деструкторе которой вызывается освобождение Mutex.
Попробуйте взять готовую реализацию семафоров на С или С++ (возможно прямо из PostgreSQL)
Спасибо. Так и собираюсь попробовать.
тогда проблема совместимости семафоров точно отпадает
Не совсем так, так как в PostgreSQL используются atomic* из C11. Скорее всего, это полностью совместимо с C++20, но лучше в этом все же убедиться.
используете из библиотеки [...] только сами функции для расшаривания памяти
Как раз расшаривать мне не нужно. Я получаю уже готовые адреса буферов и хеш таблицы их блокировок в shared memory, выделенной еще при старте PostgreSQL.
для Mutex в Rust принято создавать структуру
У меня ситуация несколько сложнее, так как spinlocks и lwlocks не поддерживают очереди, а ожидание, при необходимости, выполняется уже на блок из 128 буферов в хеш-таблице. Более того, spinlocks и lwlocks сами по себе не позволяют детектировать deadlock, что требует аккуратного подхода к порядку их использования.
P.S. Вообще хочу просто свой (условно, так как он на основании ряда опубликованных научных статей) тип индекса попробовать. Есть все шансы, что для более узкой области применения он окажется существенно эффективней btree_gist.
С семафорами делать нужно ровно то же самое, что и в других языках.
В Rust то как раз очень даже можно, ни одной из вышеперечисленных проблем в Rust нет.
В смысле нет? Куда они делись? Я так понимаю что проблемы с целочисленным переполнением никуда не делись, i + 1 на расте будет работать как работает соответствующая инструкция процессора. В debug они выбрали панику, но в релизе тот же самый wrap around.
но в релизе тот же самый wrap around
Так в этом и проблема, что не "тот же самый". x + 1 < x
в C и C++ не определено и компилятор может с чистой совестью весь код в соответствующей ветке if
удалить (и не только удалить, программа с UB это вообще не программа и гарантированного поведения не имеет) т.к. он недостижим, ведь по стандарту переполнение - это ошибка и UB. В Rust же это как раз таки определено и компилятор такой фигни сделать не может. Т.е. оно может работать не так, как ожидалось и хотелось, но это не UB и можно на 100% понимать, что на самом деле произойдет.
Не очень понял ваш ответ, не делать оптимизаций разве достаточно?
Я правильно понимаю что вы имеете виду что rust отключит оптимизацию, но оставит один единственный add этой платформы? Ну это же настоящий межплатформенный UB, на одной платформе получим одно, на другой - другое.
Единственное как можно избежать тут UB - это рассчитеть на поведение одной платформы, и добавить лишних инструкций на другой платформе, где поведение add отличается. Тоесть на этих платформах без UB компилер будет генерить менее эффективный код. Или я что-то не так понимаю?
Всё верно, останется поведение одной платформы, а на другой будут лишние инструкции.
rust отключит оптимизацию
UB используется для оптимизаций, но это не значит, что для оптимизаций нужно UB. какие там инструкции на разных платформах будут генерироваться не подскажу, не знаю, с точки зрения программиста и кода программы оператор +
на всех платформах сделает одно и тоже.
Ну это же настоящий межплатформенный UB, на одной платформе получим одно, на другой - другое
А вот тут стоп, это очень важно. Далеко не каждое "плохое", "не очевидное", "странное", " вредное" и т.д. поведение является неопределенным. У вам вообще оксюморон получился, определенное (если на платформе А выполняется Х, а на платформе Б выполняется У, то поведение очень даже определено) неопределенное поведение. Это было бы platform dependent behavior (или implementation-defined behavior, я не уверен, я не программирую на C++). А еще есть unspecified behavior и compiler specific behavior. И это все разные сорта грабель на любой вкус и цвет. Про UB в комментариях под этой статьей много кто много чего полезного рассказал, можно их посмотреть. Еще в комментариях под моей статьей тоже много про это писали, там тоже можно много полезного узнать.
Да и разные инструкции процессору при разных обстоятельствах даже implementation-defined behavior, если компилятор вместо простого сложения чисел в цикле сразу на этапе компиляции это вычислит и оставит от функции только ret 100
это же не UB. С точки зрения программы это эквивалентно, результат то один и тот же.
добавить лишних инструкций на другой платформе
Опять же, не готов тут что-то сказать, понятия не имею что может быть на разных платформах. Нo в Rust числа не только оператором +
складывать можно, есть еще пачка методов, типа checked_add, overflowing_add, saturating_add, unchecked_add, wrapping_add, wrapping_add_unsigned, а если этого мало, то есть еще интринсики, в которых (возможно, я не проверял) можно прям нужные инструкции +- напрямую использовать. Так что если хочется под конкретную платформу заоптимизироваться, то в этом никаких проблем нет.
Ну кажется ваш собеседник именно это и имел ввиду, что нельзя просто так бесплатно сделать поведение определенным. Будет цена в виде оверхеда на платформах которым не повезло.
А пачка интринзиков с опредененным поведением есть и в gcс, так же как опция -fwrapv, которая делает определенным поведение +.
Дык, я, конечно, не эксперт.
Но шуруповерт удобнее отвертки для кучи саморезов, хотя и отвертка будет с нами еще долго)
Думаю, что аналогия ясна)
Но шуруповерт удобнее отвертки для кучи саморезов, хотя и отвертка будет с нами еще долго)Думаю, что аналогия ясна)
Я бы рассматривал язык как ящик инструментов. Ну и любая аналогия ложна.
С++ и Rust это универсальные языки, было бы расточительно, создавать ПО на разных яызках для каждой платформы. К примеру на Windows C++, на Linux Rust, Mac Cobject, IOS Kotlin, Android Java
Это прям, распыление ресурсов. Так же и для других общих задач. Удобно когда это один язык + фреймворки и библиотеки.
Думаю, что аналогия ясна)
Классическая аналогия выглядит чуть иначе (анекдот №10042198 :-) ):
«Хозяйке на заметку. Шуруп, забитый молотком, держится крепче, чем гвоздь, закрученый отверткой.»
И еще я хочу сказать именно тут так как сам спор связан с этим. Нынешняя тема имеет шанс превратиться в музейный экспонат. Дело даже не в ИИ, хотя он может многое изменить. Сейчас выросло поколение, которое ничего кроме смартфона не знает и знать не хочет. Нам говорят что интересы пользователя обеспечиваются проверкой кода. Но кто будет ее делать? Поколение смартфонов хочет жрать морожение и купаться в теплых морях, и оно право. Эти молодые не имеют ни малейшего терпения. Поэтому корпорации начнут безобразничать так что мало не покажется. Сферу айти ждет либо полный абзац либо огромные перемены.
Поколение смартфонов хочет жрать морожение и купаться в теплых морях, и оно право. Эти молодые не имеют ни малейшего терпения.
Да, да слышали. И Пифагор ругался в своё время на молодежь. Им бы на свирелях играть и спать под виноградной лозой, ленивые они жопы:)
Вы так пишете, будто корпорации состоят из ”морсеАнов”.
Пост конечно слабоват, уровень 'дурак! - сама дура! " но я понимаю что автора сподвигло.
Однако, комментарии удручают. Единицы понимают всю ценность опасного кода. Зумеры, увы, нет.
Вообще, я конечно за то, что бы можно было произвольно миксить safe\unsafe код, в пределах даже одной функции. Но, это я, много хочу :)
Я тут глянул на gtk-rs в Gnome Builder, после чего желание дальнейшего знакомства с Rust отпало полностью. Отличный пример так называемого overengineering при почти нулевом выхлопе.
А что с ним не так?
Раздуто, перемудренно, нелогично
Уточню: что с ним "не так" такого, что было бы "так" в самом gtk?
Ответ на «Коротко о том, почему Rust лучше C/C++» или меня не бомбит