среда, 17 июля 2019 г.

[prog.flame] Почему Rust может быть намного более пригоден для прикладного программирования, чем C++

Несколько запоздалое дополнение к двум постам с прошлой недели (раз, два). Хочу немного поговорить о неоднократно услышанном от Rust-евангелистов мнении, что Rust отлично подойдет не только для системного (и околосистемного) программирования, но и для прикладного.

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

Прежде чем пойти дальше отпрыгнем в сторону и попробуем хоть как-то определить само понятие "прикладного программирования". По большому счету к прикладному программированию можно отнести все, что не касается системного и низкоуровневого программирования. Т.е. если код не решает каких-то специфических задач, интересных только программистам (ядро ОС, компилятор, линкер, MQ-шный брокер, сервер БД, утилита типа grep и т.д.) или не управляет каким-то оборудованием (вроде бортового ПО автомобиля или самолета), то это прикладное ПО. К сожалению, в этом случае в "прикладное ПО" попадает слишком большой спектр софта: скажем, от Microsoft Word или Adobe Lightroom до склепанного на коленке в Wordpress сайта-визитки. Но есть подозрение, что если попытаться конкретизировать термин "прикладное ПО", то возникнет ощущение насильственной подгонки условий под нужный результат. Посему более жестких условий накладывать не будем.

Итак, есть тезис о том, что Rust отлично подойдет для прикладного программирования...

Поскольку я в программировании уже не один десяток лет, то я помню массовое использование C++ для разработки прикладного ПО. ИМХО, было это не от хорошей жизни, а в силу обстоятельств (слабые компьютеры, совсем другой набор языков и инструментов для разработки, другая организация труда и т.д.). Тем не менее, результат вряд ли можно назвать хорошим. Для каких-то типов ПО, вроде Word-а или Lightroom-а, С++ был еще нормальным выбором. Но для других типов ПО C++ не подходил вообще. И поэтому C++ начали очень активно вытеснять появившиеся и развившиеся со временем Delphi, Java, С# и пр. Что, опять же, ИМХО, вернуло C++ туда, где он и должен был бы быть.

Т.к. Rust создавался как замена C++, то интересно поразмышлять на предмет того, а будет ли результат применения Rust-а для разработки прикладного ПО таким же печальным, как в случае C++? Или у Rust-а шансов побольше?

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

Безопасность по умолчанию. Одна из самых больших проблем при широком применении C++ -- это его небезопасность. Идеология C++ в том, что C++ позволяет делать все, что программисту заблагорассудится в предположении, что программист знает, что делает. Опыт говорит о том, что это не так. Даже те программисты, которые знают, что они делают, имеют свойство ошибаться. Что уж говорить о программистах, которые даже не знают, что делают. А чем дальше в прикладуху, тем больше таких программистов. Поэтому элементарная проверка индексов при обращении к элементу массива в run-time -- это must have для прикладной разработки. В Rust-е это есть. И это означает, что криворукие разработчики хотя бы не будут расстреливать память и программа будет падать сразу в месте возникновения ошибки, а не через пару-тройку часов-дней-месяцев после.

Унифицированное управление памятью. Более-менее сложный софт компонуется из разных компонент. Какие-то пишутся специально под проект, какие-то заимствуются из прошлых разработок, какие-то берутся просто на стороне. И регулярно встречаются ситуации, когда один компонент создает объект, а дальше объектом владеет другой компонент. Соответственно, объект разрушается не там, где он был создан. Да и вообще, объект должен быть разрушен после того, как перестает быть нужным.

Лучше всего с такими проблемами справляется сборщик мусора. Но если GC нет, то возникает вопрос: как же работать с памятью?

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

Например, до идеи использования умных указателей пришли далеко не сразу. Поэтому распространенной практикой было возвращение голых указателей. Естественно, кто-то забывал затем освободить память. Кто-то, напротив, освобождал не ту память. Или делал это не вовремя. Даже когда умные указатели стали получать распространение, то оказалось, что в стандартной библиотеке языка их нет, поэтому каждый стал обзаводиться собственным набором этих самых умных указателей. И можно было столкнуться с тем, что одна библиотека использует свой класс smart_pointer-а, а другая -- свой. И с этим нужно было что-то делать.

Тогда как в Rust-е, насколько я могу судить, такой проблемы нет. Как благодаря borrow checker-у, так и благодаря наличию Box, Rc и Arc прямо в стандартной библиотеке.

Унифицированный подход к информированию об ошибках. В общем-то по аналогии с предыдущим пунктом. В C++ всегда был зоопарк способов информирования о возникшей ошибке. Даже еще до того, как в С++ завезли исключения. А даже когда исключения завезли, класс std::exception появился не сразу. А даже когда он появился, то далеко не сразу его стали использовать в качестве базы для собственных классов исключений. Да и сейчас не всегда используют, к сожалению.

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

А вот в Rust этого зоопарка, к счастью, нет.

Приличная стандартная библиотека вот прямо сразу. Большой бедой C++ всегда был объем его стандартной библиотеки. Если вспоминать C++ до 98-го стандарта, то стандартная библиотека C++ была вообще никакой. А ведь именно в этот период C++ начали затаскивать в нишу прикладного ПО. Как следствие появился зоопарк больших фреймворков, каждый из которых давал разработчику то, что, по хорошему, должен был дать сам язык. MFC от Microsoft, OWL и VCL от Borland-а, Qt от TrollTech и т.д. В каждой были свои строки, свои контейнеры, свои средства работы с файловой системой и т.д., и т.п. Естественно, никак не совместимые между собой. Поэтому, если у вас был большой кусок прикладного кода, написанного на MFC, и вам нужно было его переиспользовать, скажем, в Qt, то... То скорее всего мы могли этот кусок только переписать заново.

Опять же, к счастью, в Rust этого зоопарка нет.


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

2 комментария:

TheIvan комментирует...

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

XX комментирует...

Невозможность определить, какое ПО является прикладным - как бы намекает, что "системное программирование" не так уж и четко отделено от прикладного. Rust позиционируется как системный язык , который, тем не менее, годится и для прикладной разработки. И это, я думаю, ошибка: наоборот, Rust - прикладной язык, на котором можно писать и низкоуровневые системные вещи (поэтому в нем есть ключевое слово unsafe, а не safe). Rust снимает во многом искусственный барьер между прикладным и системным кодом, делает программы полноправными. В этом его объективная сила, которая многими не понята. Этим он продолжает поступательное восхождение в традиции С++, но без его legacy, и не как всякие Java - прыжками выше головы с обрубанием хвостов. В итоге Rust занимает место посередине, ближе, чем другие к "яблочку" - языку общего назначения - тогда как остальные летят в "молоко" с той или другой стороны.