[Перевод] Предпочитайте Rust вместо C/C++ для нового кода

?v=1

2019–02–07


  • Когда использовать Rust
  • Когда не использовать Rust
  • Когда использовать C/C++
  • Ложные причины использования C/C++
  • Приложение: моя история с C/C++
  • Приложение: хор

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

TL; DR: C/C++ имеет достаточно конструктивных недостатков и альтернативные инструменты разработки уже находятся в достаточно хорошей форме, поэтому я не рекомендую использовать C/C++ для новых разработок, за исключением особых обстоятельств. В ситуациях, когда вам действительно нужна мощь C/C++, используйте вместо него Rust. В других ситуациях вам все равно не следовало бы использовать C/C++ — используйте что-нибудь другое.


Когда использовать Rust

Такие приложения, как критически важные для безопасности прошивки, ядра операционных систем, криптография, стеки сетевых протоколов и мультимедийные декодеры (в течение последних 30 лет или около этого) в основном были написаны на C и C++. Это именно те области, в которых мы не можем позволить себе быть пронизанными потенциально эксплуатируемыми уязвимостями, такими как переполнения буфера (buffer overflows), некорректные указатели (dangling pointers), гонки (race conditions), целочисленные переполнения (integer overflows) и тому подобное.

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

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


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

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

(Если вам не нужны все эти критерии… тогда, смотрите следующий раздел.)

Я внимательно слежу за Rust с 2013 года и язык значительно повзрослел. По состоянию на конец 2018 года^1^ я думаю, что он достаточно зрелый, чтобы начать рассматривать его как вариант, если ваша организация спокойно относится к генерации не оптимального кода. Я был одним из первых пользователей C++11 в 2011 году, и мой текущий опыт работы с Rust лучше, чем опыт с C++11 GCC в то время. Что о чем-то говорит.

Почему 2018? Потому что теперь можно заниматься разработкой под «голое железо» и для встраиваемых систем (например, модификацией ядра), не полагаясь на нестабильные функции из ночной сборки набора инструментов Rust (nightly Rust toolchain). К тому же изменения в редакции 2018 являются превосходными.

Я поддерживаю свои слова собственными действиями. Вместо того, чтобы просто говорить, я портирую свой высокопроизводительный встроенный и графический демонстрационный код с C++ на Rust. Это код для режима реального времени, в котором важны отдельные циклы ЦПУ, где у нас нет достаточного количества оперативной памяти для выполнения текущей задачи и мы нагружаем оборудование до предела. Версия кода на Rust более надежна, часто быстрее и всегда короче.


Когда не использовать Rust

Rust выделяется там, где исторически господствовал C/C++, но в результате Rust требует от вас, чтобы вы думали о некоторых вещах, что и в C/C++. В частности, вы потратите время на рассмотрение стратегий выделения памяти. Для большинства приложений в 2019 году это напрасная трата усилий; просто сбросьте эту проблему на сборщик мусора и закончите на этом. Если вам не нужен точный контроль со стороны Rust над локальностью памяти и определенностью, у вас есть множество других вариантов.

Конкретный пример: если бы меня попросили написать вычислитель символьной алгебры (symbolic algebra evaluator), или параллельную постоянную структуру данных (concurrent persistent data structure) или что-нибудь еще, что выполняет тяжелые манипуляции с графами, то я, вероятно, обращусь к чему-то что имеет трассирующий сборщик мусора — например, что-то другое, но не Rust. Но это не будет C++, где мне пришлось бы работать так же усердно, как в Rust, но с меньшими затратами. Я бы лично подтолкнул вас к Swift^2^, но Go, Typescript, Python и даже Kotlin/Java — вполне разумный выбор.

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


Когда использовать C/C++

Вот несколько веских причин, по которым вы все равно можете выбрать C/C++:


  • Вы уверены, что ваш код никогда не подвергнется атакам, не подвержен атакам повреждения данных или на кого-то полагался. Типа, взлом прототипа на Arduino. Тогда вперед.


  • У вас есть нормативные или договорные требования для использования определенного языка. Хотя в этом случае вы, вероятно, выберете Ada, которая в первую очередь значительно менее подвержена ошибкам, чем C.


  • Ваша целевая платформа не поддерживается в Rust. Поскольку Rust поддерживает почти все, что связано с бэкэндом LLVM, включая множество платформ, которые не поддерживаются в GCC. Это довольно короткий список, но в настоящее время он включает, не поддерживаемые 68HC11 и 68000. (Rust поддерживается на MSP430, Cortex-M и т.д., поддержка AVR в процессе стабилизации). И если вы на телефоне, десктопе или сервере, который вы сами поддерживаете. Даже на мейнфрейме IBM System 390.


  • Вы ожидаете, что ваш компилятор/набор инструментов (toolchain) будет сопровождаться соглашением о коммерческой поддержке. Я не знаю, чтобы кто-нибудь предлагал такое для набора инструментов Rust. Я также не знаю, чтобы кто-нибудь предлагал его сейчас для GCC, когда был куплен CodeSourcery.


  • Вы ожидаете, что ваша система станет достаточно большой, чтобы производительность rustc стала для вас проблемой и вы ожидаете, что это произойдет быстрее, чем rustc смогут улучшить. Rustc компилируется медленнее, чем GCC. Команда внимательно следит за этим, и ситуация улучшается. Ваш опыт будет во многом зависеть от сложности вашего кода C++; один из моих проектов собирается в Rust быстрее, чем в GCC.


  • У вас есть большая кодовая база C++, которая экспортирует только C++ интерфейс, не является независимым от языка API (например, интерфейс extern "C", каналы (pipes) или RPC). Семантика C++ настолько сложна, что ни один язык не справится с ней должным образом. (Swift, возможно, подходит ближе всего.) Наличие подобной системы у вас в какой-то момент вас «укусит».



Ложные причины использовать C/C++

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


У C/C++ есть 30+ лет работы над компилятором, поэтому они будут быстрее/надежнее.

В основном я слышу это от людей, которые не работали над компиляторами. Это заблуждение.

Компиляторы C и C++ значительно улучшились за последние несколько десятилетий не потому, что мы постепенно разработали специальное понимание компиляции языка C, который был разработан, чтобы быть простым для компиляции, а потому, что мы стали лучше писать компиляторы.

Rust использует те же бэкэнд компилятора, оптимизаторы и генераторы кода, что и Swift и C++ (Clang). В большинстве случаев код работает так же быстро или быстрее, как сегодня скомпилированный C/C++.


Но у меня есть команда хорошо обученных программистов C/C++, у которых нет времени на изучение нового языка.

… у меня для вас плохие новости. Ваши C/C++ программисты, вероятно, не так хорошо обучены, как вы думаете. Я работаю в месте, где все имеют очень твердое мнение о C++, которое они не хотят менять, работаю вместе с одними из лучших программистов на планете. И тем не менее, при проверке кода я все еще регулярно ловлю их на допущенных ошибках или коде полагающимся на неопределенное поведение (UB). Ошибки, которые они не допустили бы в Rust.

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


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

Для C программистов, смысл в том, что они пытаются сделать сначала назойливый двусвязный список, который бывает невозможно выразить в безопасном Rust (но мы над этим работаем). Это достаточно распространенная жалоба, поэтому существует целый учебник, относящийся к ней, Learning Rust With Allly Too Many Linked Lists.

Его также очень сложно сделать правильно в C/C++ и я могу практически гарантировать, что вы написали такой один, но он просто не корректен для много поточной / SMP среды. Вот почему его также трудно выразить в Rust.

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


Приложение: моя история с C/C++

Я не тот парень, который пробовал C++ и думал, что это сложно. Я прошел долгий путь, чтобы прийти к этому.

Я использую Cи примерно с 1993 года, а C++ с 2002 года, оба более или менее постоянно. Я использовал их в разных окружениях, включая продакшн в Google, Qt, Chrome, графические демонстрационки, ядра ОС и встроенные микросхемы управления батареями. При создании микропрограммной компании Loon, я твердо выступал за C++ (версии С99); мы быстро перешли на C++11 (в 2011 году), и черт возьми, это окупилось. Позже я вложил много энергии в то, чтобы убедить другие команды в X использовать C++, а не Cи для их прошивок.

Когда Loon не смог найти работающий на «голом железе» C++ код crt0.o для тогда еще новых процессоров Cortex-M, я написал его; они все еще работают на нем. Я написал замену стандартной библиотеки C++, которая устраняет выделение памяти в куче и добавляет некоторые Rust-о подобные возможности. Я знаю стандарт C++ не обычно хорошо… или, по крайней мере, я его изучил. Я «заржавел» в прошлом году или года два назад (каламбур).

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


Приложение: хор

Хор в который я собираю примеры умных людей согласных со мной. :-)

Крис Палмер (Chris Palmer): State of Software Security 2019: (выделено мной)


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

Эссе Алекса Гейнора (Alex Gaynor) The Internet Has a Huge C/C++ Problem and Developers Don’t Want to Deal With It (в Vice, во всех местах):


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

(У него также есть отличные статьи в блоге по этой теме.)

Manish Goregaokar из Mozilla, пишет в ycombinator что fuzzing тестирование частей Rust кода в Firefox не выявило ошибок безопасности, но помогло найти ошибки в C++ коде, который он заменил:


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

Это было более или менее нашим опытом с fuzzing кода Rust в firefox, ух… Фаззинг обнаружил множество паник (и отладочных ассертов / ассертов о «безопасном» переполнении). В одном из случаев он действительно обнаружил ошибку, которая не была замечена в аналогичном коде Gecko около десяти лет.

Copyright 2011–2019 Cliff L. Biffle — Contact

© Habrahabr.ru