Pull to refresh

Ответ на «Коротко о том, почему Rust лучше C/C++» или меня не бомбит

Level of difficultyEasy
Reading time11 min
Views23K

Прочитал недавно данную статью. Будем считать, что всё же её написал живой человек, а не ГПТ, хотелось бы надеяться. Скажу честно, хоть и автор уверял, что статья наполнена объективным сравнением, описаны плюсы и минусы двух языков, но на деле, посыл статьи получился, что Rust это Д’Артаньян от мира ИТ, а остальные не Д’Артаньяны...

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

Толчком к её написанию были многочисленные «горячие» споры в комментариях под статьями Хабра о том, почему Rust или C/C++ лучше. Я же попробую занять объективно-нейтральную позицию в этом споре, поэтому о минусах тоже будет речь.

Горячие споры как раз разжигают Rust программисты под статьями о С++. И обычно они начинаются с фразы, а вот в Rust'е... Само по себе Rust сообщество очень токсичное. И все мы помним инцидент произошедший не так уж и давно. Если вкратце, чувак написал на расте проект, но Rust сообщество набросилось на него как стая гиен, с утверждениями, что у автора код говно, проект говно и сам он говно. А причина в том, что он якобы не использует некий стиль растовиков, переменные не так называются, методы не в том case стиле и вообще так не пишут на Rust. Итог очевиден, автор со словами я ипал я увольняюсь, свалил с данного языка, ушёл в гугл и пишет код на С++ попивая своё винишко с Мальдив:)

Унифицированность

Давайте для начала обсудим заметное количество существующих компиляторов C/C++. Это преимущество или недостаток? Многие сторонники C/C++ уверенно говорят о том, что это огромный плюс. Но действительно ли это так? Давайте погрузимся в эту тему и рассмотрим ее более детально.

Да, это хорошо и большое количество С++ компиляторов обусловлено временем появления С и С++, это примерно 40-50 лет назад. И тогда ещё не было ни GCC ни LLVM, с их гибкой архитектурой компиляторов, разделенных на бэк и фронт. Неожиданно? Открытого софта, в том понятии как сейчас, с большим количеством программ, вариантов дистров просто не было. Айти только зарождалось, отрабатывались архитектуры и концепции более универсальных и многофункциональных ОС.

И в то время каждая коммерческая фирма пилила свой компилятор, утилиты, библиотеки. Вот такой исторический экскурс малята. Погружение продолжается...

Спросите у любого опытного программиста C/C++, и он подтвердит, что попытка скомпилировать программу, написанную на C/C++, с использованием MinGW или Clang без каких-либо изменений в коде, скорее всего, не увенчается успехом. Причина этого кроется в том, что каждый из этих инструментов использует свои собственные реализации стандартных функций, операторов, библиотек и так далее.

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

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

А вот и нет. Кроме компиляторов С++ есть такое понятие как стандарт, если переводить на растовский язык, у вас есть книга с описанием Rust'а, как работает, ключевые слова, семантика, синтаксис и остальное. Так вот, у вас, а не у нас есть некий мануал, а вот уже у С++ программистов имеется не просто мануал, а целый международный стандарт, с описанием всего языка С++ и абстрактной машины на котором он будет выполняться, с учетом множества архитектур, разрядностей, и ещё немалого количества вещей нужных для разработки кроссплатформенного кода. Что бы программа написанная на конкретном стандарте, не только собиралась с помощью любого компилятора для любой железки и с поддержкой данного стандарта, но и правильно работала скрывая все "шероховатости" конкретного железа на котором исполняется.

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

Остановимся на совместимости, расскажу о своём опыте, думаю для вас будет откровением, на сколько совместимость С++ тщательно соблюдается.

Я пилю проект LDL, если вкратце это аналог библиотеки SDL, но в том числе с поддержкой старых систем и главное преимущество в том, что LDL имеет единый API для всех поддерживаемых на данное время систем и платформ и так же для будущих на которые смогу портировать.

Первая итерация проекта написана на стандарте С++ 98, не использует компилятороспецифичный код. И собирается следующими компиляторами:

  1. Visual C++ 98 и выше вплоть до последней версии 2022

  2. Borland C++ 6.0 и выше (уверен, что соберется и на других версиях, я просто не проверял)

  3. GCC 3 и выше вплоть до последней GCC 14

  4. Clang не проверял, но уверен, что соберется.

    Вторая итерация в начале была экспериментом, по расширению списка поддерживаемых старых компиляторов. Поэтому перешел на достандартный С++, который еще не поддерживал шаблоны и вообще на дворе был неолит. Что позволило добавить в том числе поддержку Borland C++ 2.0 (MS-DOS) и ведется работа над добавлением других компиляторов, Watcom, Symantec, Visual C++ 1.5 и выше

    Поддерживает следующие системы:

  1. Windows 95 и выше

  2. Linux Debian 3 и выше

  3. MS-DOS 16 и 32 битный

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

И знаете как мне это удалось? Я просто использовал конкретный стандарт языка, в первом случае С++ 98, во втором скажем так общий стандарт С++ до шаблонов. Ну не прелесть ли? Что вы сейчас думаете о совместимости С++?

Инфраструктура

Никто в здравом уме не будет отрицать преимущества полноценного тулчейна с возможностью расширения, менеджера пакетов, тестирования и т. д., которые предлагает Rust, по сравнению с C/C++. Это преимущества, которые определенно улучшают процесс разработки программного обеспечения и предоставляют разработчикам более современные инструменты для работы.

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

Из этого следует, что одной из ключевых проблем C/C++ является работа с библиотеками и их сборка. Для сборки одной библиотеки может потребоваться загрузка и установка большого количества дополнительного программного обеспечения, что может быть сложно и затратно по времени, а для другой библиотеки - уже другого. Это не только увеличивает сложность процесса, но и может привести к проблемам совместимости.

В Rust есть официальный менеджер пакетов Cargo, который позволяет делать практически все: от создания проекта до его сборки, запуска тестов и деплоя. Это делает процесс разработки более простым и понятным. Дополнительным преимуществом является возможность расширения функциональности через так называемые крейты.

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

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

Проблемы совместимости могут возникнуть везде, и питон 3 это не обошло. Когда меняют язык или ядерные вещи, могут быть проблемы. Но в защиту С++ скажу, С++ никогда не ломает обратную совместимость, вы просто берете и собираете код компилятором с поддержкой нового стандарта, у вас могут появится новые ворнинги, так как компиляторы улучшаются и добавляюся новые опции, анализирующие код при компиляции.

Проблема не стандартного тулчейна есть, но имеет несколько решений. Вот как-то так.

Строгий компилятор и безопасность Rust

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

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

Уникальная особенность — это хорошо, было бы странно если бы Rust не воспользовался накопленным опытом и создал бы дырявый язык. Просто С был первым, С++ вторым и причем в бородатые годы, когда ещё не понимали, как оно лучше, а весь упор был направлен на производительность конечной ОС или программы. Процессоры тех лет имели производительность в десяток mhz и на фоне этого пользователь требовал: игры, программы, профессиональный софт, гуй и остальные радости. И программистам приходилось экономить память, увеличивать производительность обработки данных. Суровое было время. Конечно, сейчас производительность просто расплескивается направо и налево. Но это уже другая история.

А теперь насчёт строгости. Берем последний компилятор GCC 14 включаем все предупреждения компилятора + просим компилятор рассматривать любое предупреждение как ошибку используем более новый стандарт >= C+ 11 и о чудо. У нас не компилится проект по причине того, что компилятор считает программиста, обезьяной с клавиатурой в руках. Прям как в Rust'е. Опять неожиданно? То есть не меняя язык, мы можем прописав несколько опций компилятора, не только повысить безопасность, но и сделать его более надежным.

Но не обольщайтесь, тесты всё равно нужно писать и организовывать фазинг тестирование.

Вдобавок, стоит отметить, что часто упоминают о том, что такие концепции, как result pattern и другие, далеко не новы для Rust, и что подобные идеи были реализованы и в других языках программирования. Это, безусловно, так. Например, в C++ есть умные указатели, optional и множество других элементов. Однако, стоит учесть, что все эти функции были введены в C++ уже после его создания, поэтому они реализованы на уровне библиотек и их использование не является обязательным. Это значит, что разработчики могут выбирать, использовать их или нет. В отличие от этого, Rust изначально включил в себя лучшие практики и идеи из мира программирования и использует их на уровне языка, делая их использование неотъемлемой частью процесса написания более безопасного кода.

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

Понятно, что если захотеть можно и причинное место сломать. Ну вы поняли.

Производительность

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

Однако, стоит отметить, что сравнение производительности двух языков программирования - это задача довольно сложная, даже если они используют один и тот же бэкенд*. Сложность заключается в том, что разные компиляторы могут применять разные стратегии оптимизации и концепции, которые в свою очередь могут значительно влиять на производительность. Даже абстракции, используемые в языке, могут иметь значительное влияние на это**.

Ну тут да, это нужно мерить. Вам и мне лень. Много факторов и т.д

Кроме того, установка расширений Cargo, позволяющих создавать шаблоны проектов, может делать процесс еще более упрощенным. Это дает программистам возможность быстро настраивать новые проекты и экономить время на стандартных задачах, ускоряя процесс разработки и повышая его эффективность.

А вот и нет. Мы не в вакууме кодим, а применительно к задаче. Это как минимум не всегда консольные утилиты с минимальным функционалом. Это GUI, графика и остальное непотребство. И то, что расширение подставится быстрее и с первого раза соберется, не отменяет задачу закодить проект используя стороннюю либу, скорее всего написанную на С\C++. Вот как раз в Rust'е если данной либы нет, значит нужно писать обертку или как оно там у вас называется. Ну комон, это не серьезно.

Неопределённое поведение C/C++

В языках программирования C/C++ существует обширный спектр случаев неопределённого поведения, которые могут стать причиной серьёзных проблем и нестабильности в рамках написанного кода. Это широко вариативно включает в себя все возможные аспекты, начиная от непредсказуемых результатов выполнения функций и заканчивая возможностью выполнения произвольного кода. Это может привести к неконтролируемым и непредсказуемым результатам, которые в свою очередь могут вызвать критические ошибки в работе программы, нарушить её стабильность и даже привести к потенциальным угрозам безопасности.

С другой стороны, в языке программирования Rust, была применена другая концепция управления ошибками и неопределенным поведением. Компилятор этого языка применяет строгий контроль над всеми возможными случаями неопределённого поведения. Это означает, что каждый потенциальный случай неопределенного поведения будет проверен и обработан во время компиляции, предотвращая возможность его проявления во время выполнения программы.

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

Rust молодец, теперь про UB содомию. Я уже писал, что С++ был продолжателем дела С, писать переносимый и быстрый софт, так как железки того времени тянули только код с UB с приемлемой скоростью. Это было сделано именно для этого, что бы у компилятора было больше свободы оптимизации. Такое решение, но тогда оно было оправдано. Важен контекст. Теперь возвращаемся в наше время.

Мне это напоминает анекдот:

Доктор, когда я так делаю мне больно!

Так не делайте так.

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

Баги о которых вы говорите, вылавливаются при тестировании. Я уже писал в статье, но повторю тезис:

То есть вы не сможете выделить память и просто забыть её освободить, тем самым вызвать утечку памяти. Когда программа съела всю память и ОС ушла в свап. Компилятор Rust включит все эвристики и будет мучить вас до тех пор пока вы не гарантируете средствами языка корректную и безопасную работу с памятью.

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

А это:

  1. Логические ошибки.

  2. Численные переполнения.

  3. Переполнение стека.

  4. Проблемы с выравниваниями.

  5. Сложные проблемы многопоточности.

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

На эту тему есть отличный доклад Антона Полухина.

Итого

Я не буду цитировать текст статьи. А сразу накидаю свои итоги.

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

И как раз поэтому всё что-то связанное с производительностью пишут на С\С++. И полно вакансий на нём. И данный поток вакансий не угаснет. Потому что нужны быстрые браузеры пережёвывающие тонны JavaScript. Нужны быстрые бекенды, софт для моделирования, игры, движки и т.д

Все учебные материалы — это С\С++, как делать операционки, игровые движки, графические API от этого невозможно уйти. Поэтому как бы не силилось Rust сообщество, поезд ушел и только набирает скорость. Такова жизнь.

Я думаю, что со временем С++ вберет в себя идеи Rust'а. Может не так элегантно, красиво, но всё к этому идёт.

Всем добра!

Only registered users can participate in poll. Log in, please.
Ну, чё гоу на раст?
32.08% Таки, да230
35.01% Таки, нет251
32.91% Таки, надо подумать236
717 users voted. 93 users abstained.
Tags:
Hubs:
+37
Comments292

Articles