[Перевод] Предпочитайте Rust вместо C/C++ для нового кода
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