Rust для веб-разработчика — быстрый старт и стремительный полет

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

    Краткая история ценностей в программировании


    Чтобы материал хорошо залег в голову и сердце, неплохо кратко вспомнить, что люди хотели сделать в программировании за последние 50 лет и что в итоге у них получилось. Без обид, только личное субъективное мнение и холивар, подкрепленный 20-летним опытом разработки.

    Низкоуровневые языки: C, C++


    Понятно, что можно писать программу сразу в виде цифр на машинных кодах и многие этим занимались на ZX Spectrum, БК0010-01 и на PC — код получается очень быстрым :-) Но мы люди, в голову много информации не помещается, мы отвлекаемся и поэтому даже изобретение ассемблера не особо помогло — код на таком низком уровне пишется очень редко и очень метко и, скорее всего, если вы не занимаетесь разработкой драйверов, микроконтроллеров или хитрых встраиваемых систем, это в жизни не пригодится.


    В начале 70-х в Bell Labs придумали язык C, который прижился благодаря лаконичному синтаксису и очень «дешевым» абстракциям, практически став «переносимым ассемблером». Понятно, что если принять постриг, 10 лет писать на C ночами, не есть мясо, молиться и не отвлекаться на соцсети и формы прекрасного пола, то можно писать очень даже полезные и быстрые программы, о чем красноречиво свидетельствуют GNU, прекрасные производительные игры, не всеми любимая, но безальтернативная по качеству Windows и еще можно привести много примеров.
    Но обратная сторона медали постоянно дает о себе знать — регулярно открываемые дырки в безопасности (создана целая индустрия по «дыркам в софте»), обусловленные дырками в концепции самого языка С — компилятор подобен безответственному быку, с неимоверной мощью, интенсивным оргазмом и короткой памятью. Любая неосторожность — и можно не просто уронить программу (разыменование нулевого указателя, двойное освобождение указателя, выход за пределы массива), а безвозвратно испортить данные и долго этого не замечать, пока не начнут звонить клиенты и когда уже поздно («неопределенное поведение», отличающееся от компилятора к компилятору).

    Бьёрн Страуструп только еще больше запутал ситуацию в начале восьмидесятых, добавив возможности ООП к C. Несмотря на большую популярность, С++, в целом, видится как серия экспериментов над программированием, как таковым, с разной успешностью исходов, в т.ч. летальных. Иногда даже кажется, что смысла в C++ не было с самого начала, либо он был постепенно утерян, родив в результате нагромождение объективно переусложненных и противоречащих друг другу концепций, которых становится все больше и больше с каждым новым стандартом. Несмотря на отличную цель «zero-cost абстракции», позволяющие получать быстрый код, для создания надежного решения, как и на С, требуется выполнение следующих условий:
    • опытная, от слова «очень», команда (годы практики, «монахи» программирования)
    • хороший статический анализатор кода
    • департамент тестирования (в коде могут быть дырки, оставленные безответственным компилятором, которые долго могут давать о себе знать)
    • согласованные всеми членами команды требования, которые тщательно контролируются (не используются «сырые» указатели, строгая инкапсуляция, жесточайшая дисциплина именования переменных, инициализация объектов и т.п.)


    Понятно, что соблюдение указанных требований, особенно в контексте возрастающей потребности бизнеса на «работающий без внезапных сюрпризов код», очень дорого. Код в подобных проектах пишется долго, тестировать его нужно продолжительное время и тщательно, но, иногда, без C/C++, до изобретения Rust, действительно сложно БЫЛО обойтись.

    Еще раз резюме по С/C++ — мы имеем мощный, но «безответственный» компилятор с «текущими абстракциями», очень мало помогающий разработчику. В результате все проблемы перекладываются на плечи программиста. Если хоть один программист в команде не опытный, не очень осторожный и не знает всех тонкостей работы компилятора (на самом деле никто не знает всех тонкостей и их находят пользователи потом) — жди беды. Но зато программа работает быстро и, вероятно, правильно :-) Это, разумеется, породило целый рынок «костылей» — статических анализаторов, платить за которые должен, как оказалось, Заказчик. Возникает вопрос — а разве нельзя было что ли написать более строгий и безопасный компилятор, помогающий разработчику и рождающий программы без сюрпризов и низкоуровневых дырок в безопасности?

    Java, C#, Kotlin


    Ситуация с откровенно слабым контролем «неопределенного поведения» и очень высокими требованиями к разработчикам в C/C++ породило желание сделать безопасную среду разработки, в т.ч. для интернета, доступную большинству желающих. Так в конце 90-х появилась Java.
    В принципе, теперь любой желающий с разным уровнем подготовки мог писать все, что угодно и как угодно и ОНО РАБОТАЛО и что в программу не засовывай — низкоуровневых «дырок» в безопасности и спонтанных крешей уже не было (почти, но они уже были вызваны багами в виртуальной машине и исправлялись централизованно). Оставались только логические «дырки» или скрытый медленный код (рожденный незнанием алгоритмов, понятия алгоритмической стоимости и тормозящий при увеличении объема данных), который был уже не так страшен и позволяет делать программы быстро, а при необходимости — переписывать маленькие куски квалифицированной командой на C/C++.
    Интересные и ценные вещи, которые принесла в мир Java, следующие:
    • код пишется один раз под виртуальную машину, которая работает на любых архитектурах (windows, linux, mac)
    • разработчик больше не управляет памятью напрямую, как в C/C++ — это перекладывается на плечи «сборщика мусора»; так снимается риск неопределенного поведения, незаметной порчи данных и потенциальных низкоуровневых дырок в безопасности
    • код компилируется «на лету», во время работы программы (Just_In_Time-компиляция), т.к. известно, что в программе активно и часто выполняется только малая часть, поэтому все компилировать смысла нет
    • проще стало писать эффективный многопоточный код, т.к. была строго специфицирована модель памяти (гораздо раньше, чем в C++), но, о чем далее, не все проблемы с «логической надежностью работы нескольких потоков» были решены (остались возможны deadlocks, например или data-races)


    Разумеется, появление такой дружелюбной платформы позволило писать много полезных и бесполезных программ, чем сразу воспользовался бизнес. А поддержка обратной совместимости в Java в 15 и более лет обусловила такую популярность технологии в мире энтерпрайза.

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

    Однако, далеко не сразу, в Java вылезли следующие проблемы:
    • из-за логической «дырки» в системе типизации, которая перешла по наследству из C/C++, в Java попал тип «Null», который и по сей день порождает много проблем и постоянно приводит к падению программ «не очень опытных» программистов, которые не знают про тип Optional и не интересуются монадами из функционального программирования
    • проекты на Java, работая иногда незначительно медленнее, требуют на порядки больше оперативной памяти, чем на C/C++, нужной сборщику мусора (у нас несколько проектов «кушают» десятки-сотни гигабайт ОЗУ); это часто не является прямо проблемой, т.к. цены на оперативную память снижаются, но «осадочек остался»
    • несмотря на jit-компиляцию и большие инвестиции в ускорение сборщика мусора (которых много и новые появляются регулярно и постоянно говорят: «попробуйте новый GC, он значительно лучше»), уже 20 лет некоторые высоконагруженные проекты на Java работают с регулярными паузами по несколько секунд, которые нельзя убрать никак (происходит вынужденная сборка мусора и как винтики в GC не крути, сборщик мусора просто не успевает все убирать, даже работая параллельно и даже пожирая больше ресурсов, чем сама программа)
    • известно, что GUI-приложения на Java иногда зависают и тормозят, вызывая гнев пользователей, из-за той же сборки мусора и надежды нет и не предвидится
    • синтаксис языка очень многословен и избыточен (нужно писать и затем читать слова «public static functon» вместо, скажем, «pub fn» или «def»), вынуждая иногда разбивать пальцы об ногти и вызывает объективную резь и кровотечение из глаз
    • резкое снижение требований к уровню квалификации разработчиков и достаточная низкая кривая вхождения породило много кода «странной степени свежести», постоянно дающего о себе знать на всю жизнь запоминающимся зловонием


    После прочитанного должно быть понятно, что все на Java писать нельзя, это будет писаться довольно медленно, потом будет под нагрузкой регулярно тормозить и зависать на секунды, «жрать» иногда много оперативной памяти (обычно съедается как минимум половина ОЗУ на сервере), производительные игры тоже не попишешь, но какие-то куски бизнес-логики, не особо требовательные к производительности и ОЗУ, особенно многопоточные — вполне можно, полезно и поэтому мы видим такую популярность технологии в мире энтерпрайза. И мы сами регулярно и активно используем Java для сервисов компании.

    C#, Scala, Kotlin


    «А когда же будет про Rust?» — погодите, нужно еще чуть-чуть подготовиться скушать эту сладкую хурму. Если не рассказать об особенностях других технологий, вы не поймете, почему появился Rust и почему от такой популярный и востребованный.

    Итак, пытаясь сделать Java лучше и прогрессивнее, Microsoft, в лице автора TurboPascal/Delphi, в начале нулевых придумал C# и концепцию .NET. Объективно, по мнению многих авторитетных экспертов, да и в курилках разработчиков, «C#» сексуальнее Java, хотя, конечно, пока еще, несмотря на Mono, сильно привязан к Microsoft со всеми втекающими и вытекающими :-)

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

    Kotlin же популярен, т.к. делает Java более эффективной и простой для начинающих разработчиков, особенно на мобильных платформах, у которых нет времени серьезно изучать программирование :-)

    Но основная проблема и в C# (на платформе .NET), а также в Scala, Kotlin и в других JVM-языках — осталась. Сборщик мусора, Карл, создающий заметную нагрузку на сервер и остановки выполнения кода на секунды при нагрузках и прожорливые требования к оперативной памяти! И сколько еще языков со сборщиком мусора и jit-компиляцией не придумают, указанные проблемы останутся и надежды пока нет, даже в теории.

    Скриптинг


    «А как же PHP?». Да, сейчас поговорим про скриптинг. Казалось бы, зачем он? Примерно в конце 80-х стало очевидно, что, если нужно быстро решить задачу с помощью кода, не обязательно супербыстрого, причем безопасно, без неопределенного поведения и низкоуровневых дырок, можно написать скрипт. Скрипты писали и раньше, на bash, perl, awk, но, оказалось, что на python можно писать большие-пребольшие скрипты, особенно научные, причем годами!

    Lua нашел свою нишу в геймдеве и машинном обучении (Torch), JavaScript — в веб-разработке как на стороне браузера, так и на стороне сервера (все же знают, что авторизация в инфраструктуре «npm» Node.js переписана с Node.js и Golang на «rust»?). Python — в машинном обучении и анализе данных, а также в системном скриптинге. А PHP — прекрасно справляется с серверными задачами по веб-разработке.


    Плюсы скриптинга очевидны:
    • Безопасный, эффективный код без неопределенного поведения и низкоуровневых дырок в безопасности, использующий популярные алгоритмы и структуры данных (списки, словари, очереди), создается буквально за минуты
    • Очень быстрый старт и пологая кривая вхождения. Достаточно нескольких дней, чтобы разобраться и написать полезный скрипт на python, поменять логику работы nginx через скриптинг на Lua или сделать интеграцию интернет-магазина на PHP
    • Если писать скрипты дисциплинированно, соблюдая CodeStyle и максимально строго, то можно очень быстро решать сложные задачи и создавать большие программные продукты: родилось огромное количество научных библиотек на python, системы управления бизнесом и сайтами на PHP, нейросетевая платформа Torch на LuaJit, игровой скриптинг в очень известных и популярных играх и т.п.
    • Некоторые виды многопоточного программирования, где узким местом является ввод-вывод, довольно эффективно решаются на python. А используя последние возможности python с futures типа «async/await» можно еще проще решать задачи обработки большого числа сетевых сокетов
    • Очень элегантно задача асинхронной обработки большого числа сокетов даже без многопоточности решена в Node.js из коробки
    • Создавать веб-страницы, использовать многочисленные unix-библиотеки, ходить в базу данных по-прежнему легко и удобно с помощью PHP и продуктов на его основе
    • На python, PHP, JavaScript очень удобно проверять гипотезы и делать прототипы за часы, а не месяцы
    • Для аналитики и обработки данных традиционно очень удобно использовать python/R в соединении с большим количеством качественных библиотек (pandas, scikit-learn, seaborn, matplotlib ...)


    Однако нельзя не знать потенциальных минусов скриптинга:
    • Низкий уровень вхождения и очень быстрая скорость написания скриптов иногда, при отсутствии должного контроля над разработчиками, рождает тонны неэффективного и трудно-поддерживаемого кода на любой платформе
    • Отсутствие компилятора, системы типов и статической типизации требует динамической проверки передаваемых в функции и методы параметров на типы и качественного покрытия кода автотестами. Иначе любое изменение в коде может сломать программу и об этом первым узнает клиент, а не разработчик (да, я знаю про TypeScript, но это же костыли и мертвому припарки)
    • Объективно и очевидно, что в языках без статической типизации и компиляции, где многие вещи происходят в runtime, нельзя использовать многочисленные оптимизации и поэтому в ряде задач скрипты работают на порядки медленнее и потребляют в разы больше ресурсов. Несмотря на попытки внедрить jit-компиляцию в python (pypy), PHP (hhvm), JavaScript (трансляция в машинный код, вау, Node.js v8), LuaJIT — давайте не будем себя обманывать: все это выглядит как слабо эффективные костыли. Причина в том, и это нужно понять один раз и навсегда, что из-за сознательной слабой типизации языков, для скриптов вряд ли когда-либо получится написать эффективный runtime, приближающийся по скорости к гораздо более строго-типизированным Java/C#
    • А в python, похоже, никогда не будет эффективной многопоточности, как в Java/C#, из-за GIL


    Однако, т.к. в большинстве бизнес-приложений, в т.ч. системах управления сайтами, CRM и т.п. большая часть времени выполнения кода тратится на запросы в базу данных, то преимущество jit Java/C# глубоко нивелируется скоростью написания решений на PHP/python/JavaScript и лично я, для создания веб-приложений, выберу 10-20 строк на PHP, чем 10 000 строк и кучу потрохов на Java/Spring. А PHP7 как-то разогнали так, что он работает быстрее python3 ;-)

    Какой вывод можно тут сделать? Не нужно все задачи решать, как сейчас популярно, только скриптингом — в некоторых случаях разумно взять другой, более подходящий инструмент, если на это есть весомые причины:
    • очень высокие сетевые нагрузки и «экстремальная» многопоточность, тысячи — десятки тысяч сетевых сокетов и т.п.
    • ограничение на использование оперативной памяти и «железа»
    • интенсивная обработка данных и вычисления, матрицы с миллионами сущностей, GPU (хотя тут может помочь python/numpy/pandas)


    Часто в компании мы практикуем такой подход:
    • быстро делаем решение на PHP/python/JavaScript/Node.js, запускаем в бой и начинаем решать задачи клиентов
    • обкатываем фичи, возможности, улучшаем сервисы
    • в редких случаях, по опыту, обычно не ранее, чем через несколько лет, часть таких, уже стабильных по функционалу, сервисов, переписывается на C/C++/Java/Golang/Rust

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

    Функциональное программирование, Lisp, Haskell, F#


    Многие, за целую карьеру разработчика, так и не приходят сюда, а вот зря. Крайне полезно понять, почему появилось ФП (функциональное программирование) и почему в некоторых областях оно так популярно.

    Объясню просто. Есть в математике такая нерешаемая проблема, как «halting problem». Если изложить очень простыми словами и совсем нестрого, зато понятно, то нельзя придумать алгоритм, доказывающий, что программа будет работать без багов. Почему? Потому-что изначально люди начали программировать агрессивно и императивно, используя:
    • мутабельные переменные
    • циклы с условиями
    • функции, создающие побочные эффекты

    И стали ошибаться. Мы видим это сейчас, наблюдая огромное количество багов как в вебе, так и в десктопных и мобильных приложениях. И как ни покрывай автотестами код, баги все равно продолжают просачиваться, разбегаться по полу и хихикать.
    Чтобы остановить этот кошмар наступления глючного и беспощадного софта, еще в конце 50-х появилось направление функционального программирования и язык Лисп. Сейчас же это семейство языков представляет, более-менее адекватно — Haskell.

    Несмотря на то, что огромное количество возможных ошибок в коде действительно устраняется за счет:
    • алгебраических типов данных и pattern-matching по ним
    • отсутствия циклов (только через рекурсию) и мутабельных переменных в пределах доступности вытянутой руки (костыли как это обойти, конечно, можно найти)
    • очень строгой статической типизации и, одновременно, очень удобного автовывода типов Хиндли-Милнера
    • очень мощных функций, с разделением на «чистые» и «с побочными эффектами», с возможностью частичного применения
    • поддержки безопасного параллельного программирования
    • поддержки комбинаторов через монады на все случаи жизни (подробнее дальше, в разделе про Rust)


    Haskell откровенно не взлетел по причине агрессивных «понтов» — чтобы хорошо понять его терминологию, нужно быть, как минимум, кандидатом наук со специализацией в области одной из лидирующих теорий в современной математике: теории категорий. А еще в Haskell явно намудрили с «ленивостью», то ли граф выполнения память переполнил, то ли выполнение началось, и память кончилась потом, что нередко больно бьет по его боевому применению. Именно поэтому солдаты в бою на территории противника бросают Haskell и берут автомат Калашникова.

    И, конечно, совсем забыли про сборщик мусора — в Haskell он, к сожалению, тоже есть, что ставит его по эффективности и производительности на одну ступень с конкурентами типа Java/C#.

    Но это не означает, что язык не нужно учить. Haskell развивает программисту, в первую очередь, мозги, так нужные для написания ясных, эффективных и легко поддерживаемых годами программ. Известно ведь, что кроме скриптинга крайне полезно знать хоть один из «настоящих» языков программирования — и Haskell тут очень достойный кандидат.

    Новые «C/C++» — Golang, D


    Системное программирование, еще в начале нулевых, реализовывало себя в основном через C/C++ (возможно, нельзя не упомянуть в этом контексте и знаменитый Forth). А мнение Линуса Торвальдса про C++ как тогда, так и сейчас не утратило своей актуальности, несмотря на попытки великого и ужасного Андрея Александреску изменить ситуацию в D (со сборщиком мусора — ну как же так, опять наступили на эти грабли).

    Однако, видимо людям надоело постоянно заниматься системным садо-мазохизмом и писать на С/C++ в рыцарских доспехах c парадигмой RAII и кучей ограничений и запретов, изначально доступных в языке и библиотеках. И вот в конце 2009 в недрах Google появляется язык Golang.
    Если быть честным и откровенным, то Golang особо ничего нового не принес в мир системного программирования, а, скорее, был сделан шаг назад и кувырок вбок. Golang представляется как урезанная по всем направлениям до минимума Java/C# (ООП порезано и упрощено до неузнаваемости) и да, со сборщиком мусора… Единственные утешения, идущее от Golang:
    • встроенные в язык «green threads» (аля корутины), но они есть уже даже в python
    • компиляция в нативный код, только непонятно, зачем (возможно, чтобы Docker работал) :-)

    И совсем мутная ситуация с пакетным менеджером. Даже в Node.js и python он есть.

    Возможно, Golang был создан в рамках конкурентной борьбы крупных корпораций, Google vs Sun/Oracle, за сердца разработчиков, но мы этого, скорее всего, никогда не узнаем :-) Очевидно, что создание «сильно упрощенной Java/C#» привлечет и уже привлекает толпы поклонников решения системных задач, но выиграет ли от этого индустрия — нам еще предстоит увидеть. Хотя Docker на Golang вот уже появился и перевернул мир в верном направлении. А еще объективная польза от Golang — это язык с низким уровнем вхождения и если нет времени изучать Java/C#, а нужно решить системную задачу, то самое то.


    Интересно на этом фоне смотрится, конечно, Swift, c «более продвинутым» сборщиком мусора и свежими идеями. Но не всегда же разрабатывать под macOS.

    Выход есть — Rust!


    Мы же взрослые люди, умеем читать между строк и понимаем, что последние 40-50 лет постоянно предпринимались попытки создать быстрый, безопасный, строгий и системный или близкий к системному язык программирования, желательно с «zero-cost» абстракциями, но, что-то не очень-то получается :-) То язык получается относительно строгий, но, откровенно иногда тормозящий (Java/C#), то очень быстрый, но безнадежно дырявый (C/C++), то еще какой-то, но с цепочкой и гирей на левой ноге — сборщиком мусора. Ну нельзя что ли хорошенько подумать и написать компилятор, который МОЖЕТ ВСЁ вышеперечисленное ОДНОВРЕМЕННО?

    Оказывается — можно. Чудо и, наверно, лучшее, что произошло за последние 50 лет в области создания инструментов программирования, случилось в середине 2010 года в Mozilla Research. Коллеги создали компилятор, который обладает следующими возможностями:
    • имеет «zero-cost» абстракции (изюминка C/C++ и мечта Бьярна Страуструпа), т.е. ничего не выполняется внезапно тяжело и долго, без разрешения разработчика, даже цепочки итераторов-фильтров-мапперов-замыканий-монад-и-девушек преобразуются в «быстрый машинный код» (серьезно, это не шутка, есть многочисленные примеры ассемблерного кода в результате)
    • имеет удобный менеджер пакетов cargo, в котором легко управлять зависимостями и версиями
    • компилятор дает гарантию безопасной работы с памятью(* — пояснение звездочки в конце поста): проверяет код и контролирует, что работа с памятью — корректна и не возникнет неопределенного поведения и «низкоуровневых дырок в безопасности», и если код скомпилировался, то, значит, сюрпризов не будет
    • компилятор дает гарантию, что память правильно, предсказуемо и однозначно освобождается после использования и поэтому в языке НЕТ СБОРЩИКА МУСОРА от слова СОВСЕМ (а это значит, что память не будет расходоваться впустую, не нужно выделять половину ОЗУ на сервере для GC и, из-за отсутствия runtime, нагрузка на сервер будет минимальной)
    • строгая статическая типизация с автовыводом типов по схеме Хиндли-Милнера, без типа Null (!), что ускоряет и упрощает процесс написания кода и значительно сокращает число ошибок еще во время компиляции
    • алгебраические типы данных из Haskell и pattern-matching по ним, что позволяет строго и точно описывать данные и обрабатывать их с автоматической проверкой всех вариантов Enumeration (Golang, кстати, так не умеет и это не планируется)
    • безопасное многопоточное программирование с проверкой необходимых условий на этапе компиляции (об этом мечтает Java, но надежды нет)
    • приложение компилируется в нативный код без рантайма (в Golang есть рантайм — сборщик мусора; в Java/C#/Erlang и т.п. конечно есть рантайм тоже, иногда серьезно замедляющий работу)
    • Великолепный и строгий движок макросов, преобразующихся в код перед компиляцией (о чем мечтал C/C++)


    Уверен, вы ощутили шок, как и я когда-то. Как такое может быть? Это невозможно! Как соединили несоединимое и впихнули невпикуемое, что не удавалось сделать за последние 50 лет. Оказалось, возможно, «просто» обобщили ошибки предыдущих языков, написали действительно грамотный и пессимистичный компилятор и ввели в обиход несколько не встречавшихся раньше в программировании, уникальных концепций.

    Rust можно представить как «легкий» и, разумеется, очень строгий и мощный по своей сути Haskell для системного программирования.
    Еще Rust оказался более безопасным языком, чем, внимание, Java :-) — нет Nulls, алгебраические типы данных и pattern-matching по ним, строгие и мощные generics и traits и действительно безопасное многопоточное программирование. Вот это точно никто не ожидал. И код быстрее получается, и ресурсов меньше ест, и ошибок в коде меньше по причине очень строгого, «похожего на Haskell», компилятора и НЕТ СБОРЩИКА МУСОРА.

    Теперь кратко обо всем по порядку.

    Как же удалось отказаться от сборщика мусора?


    А вот так! Память в компиляторе начали представлять как ресурс, «владеть» («ownership») которым может одна и только одна переменная. Если переменная выходит из «аля области видимости» (точнее «lifetime», подробности в Rust book, но все интуитивно просто), компилятор вставляет вызов деструктора (деструктора может не быть, тогда ничего не вызывается).
    Если переменная передается в функцию/метод, то владение передается туда и в конце функции деструктор будет вызван компилятором. Если переменная возвращается из функции/метода, то владение тоже возвращается на уровень выше и деструктор вызывается на уровне выше.
    Таким простым способом раз и навсегда избавились от страшной мути в C++, связанной с конструкторами-копировщиками и move-семантики. Если описание показалось сложным, попробуйте написать код с деструкторами в rust и вы увидите, что все очень строго и логично.

    Как добились zero-cost абстракций?


    В языке нет навороченного ООП и наследования, но есть мощные и гибкие traits, а также средства инкапсуляции. В этом язык, конечно, похож на Golang. Все крутится вокруг структур и методов на них, но, из-за алгебраических типов данных и pattern-matching, код получается точным и строгим (что забыли добавить в Golang, к сожалению). На структуру/данные можно создать ссылку на чтение и ссылку на запись. Но одновременно может быть создан только один тип: одна ссылка на запись или много ссылок на чтение. В терминологии языка это называется заимствование («borrowing»). Это позволяет снизить необходимость копировать объекты, а также избежать гонки данных (за счет «read-write locks», реализуемых заимствованиями). Все это, внимание, строго проверяет компилятор и если код не компилируется, значит нужно его поправить и доказать пессимистичному компилятору, что ошибок больше нет.
    Для работы со строками и другими операциями используются низкоуровневые типы slice, что служит, конечно, скорости. Однако есть проблема — строки устроены «немного сложно» и, похоже, они сами появились в языке вне воли разработчиков в результате мутации компилятора; но они работают, работают быстро и предсказуемо и на этом со строками пока все.

    Безопасное программирование и функциональщина


    Язык очень сбалансировано напичкан возможностями функционального программирования:
    • по умолчанию, переменные доступны только для чтения
    • глобальные переменные нельзя менять (на самом деле можно, но это отдельная продвинутая и не рекомендуемая техника)
    • вместо манипуляций с циклами и массивами, используется принцип итераторов-мапперов-фильтров-замыканий, которые компилируются в быстрый машинный код
    • внезапно вы узнаете, что используете монадические вычисления в вышеупомянутых цепочках, которые разумно обрабатывают ошибки, однако тут нет умничания с терминами из теории категорий


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

    Многопоточное программирование


    За счет описанных выше уникальных для языка и встроенных в компилятор возможностей владения и заимствования, не возникает проблем с порядком установки-снятия блокировок, а многопоточное программирование делается как-то легко и без сюрпризов. Если что-то не так, код просто не компилируется. Я не буду описывать детали трейтов Sync/Send, это нужно увидеть вживую, но ничего сложного и мистического не происходит: реализуете логику, если компилятор ругается, исправляете, вот и всё.

    Если хочется реализовать многопоточный сетевой сервис с неблокирующими соединениями, обрабатывающий десятки тысяч сокетов одновременно почти незаметно для процессора, можно взять готовую библиотеку и за 1-2 часа реализовать самые смелые мысли по работе с futures в функционально строгом стиле. Получаем тоже самое, что внутри Node.js и python с async/await, но с гарантией строгих алгебраических типов, работающее гораздо быстрее и с на порядки меньшим потреблением ресурсов. А если результат одинаковый, зачем же платить больше?

    Встроенные unit и интеграционные тесты


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

    Удобный пакетный менеджер — cargo


    Приводя аналогию из Java, «Maven под именем cargo» уже встроен в среду разработки, описываются зависимости и дальше магия: качаются исходники, компилируются и все работает. Удобно, кстати, полазать по исходникам, иногда очень помогает.

    Heap и стек


    По умолчанию, память для структур и объектов выделяется в стеке, однако очень легко ее выделить и потом автоматически освободить в heap. Память, как уже описано выше, автоматически освобождается при выходе ее владельца (ссылающейся переменной) из «аля области видимости» (точнее «lifetime»). А если очень нужно, умные указатели разных видов тоже есть (здравствуй Swift). Если написанное выше показалось сложным, то это не так — напишите код, выделяющий память в heap и выведите в деструкторе сообщение об освобождении памяти и все станет ясно и понятно. Подвохов никаких нет — память всегда будет освобождена и проверяет это не программист, а компилятор и еще гарантию дает.

    Что может быть очень непонятно


    На самом деле видно, что действительно получилось создать умный, сбалансированный, очень строгий и быстрый язык для безопасного системного программирования. Да, он еще молод (2 года как стабилизировался) и в библиотеках еще не все, говорят, есть, хотя я нашел все что нужно, от скоростной неблокирующей обработки сетевых сокетов, парсинга аргументов командной строки и подключения к AmazonWebServices до углубленной криптографии c tls/ssl, которую пришлось немного под задачу доработать и это не вызвало никаких проблем.
    Однако, некоторые вещи, которые, к счастью, встречаются в реальных задачах очень редко, все-таки потребуют углубленного изучения. К таким особенностям я бы отнес понятие «аля областей видимости» (точнее «lifetimes») и их использования.
    Еще, пока видимо не очень прямо удобным местом является малое количество сред разработки. Неплохо ведет себя IntelliJ с модулем для rust, но иногда не справляется с подсказкой сложных типов. Однако можно писать и в Notepad++ — умный компилятор с гарантиями предупредит вас о ошибках в коде.

    Общие принципы разработки на Rust


    • Поймите, что вам действительно нужно написать очень быстрый системный код, работающий под высокими нагрузками и потребляющий минимум ресурсов и без всяких накладных расходов типа сборщика мусора. Если нет, напишите скрипт на python/PHP/Node.js и вернитесь к задачке через 2-3 года (в 97% случаев возвращаться не приходится).
    • Не бойтесь Rust. Основная изюминка языка — очень умный и математически строгий компилятор, который НЕ ПРОПУСТИТ опасный и некорректный код. Методом проб и ошибок, обходя острые углы, вы научитесь с помощью компилятора программировать строго и безопасно :-)
    • Начните изучение языка с "Rust book". Прекрасная, сексуальная книжка, читается легко и увлекательно. Но ее лучше прочитать 2 раза, «прорешивая» все примеры.
    • Очень помогает "Rust cookbook". Ее тоже желательно «прорешать» и посмотреть, как работает код, пощупать его.
    • Есть литература про Rust для продвинутых пользователей, например Rustonimicon, но я не рекомендую забивать себе голову действительно сложными техническими вещами без нужды.



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

    Детали реализации проекта


    Раскрою немного детали проекта. В очередь Amazon SQS льются сотни пакетов данных в секунду. Очередь читается, разбирается воркерами локально, каждое сообщение обрабатывается брокером и перенаправляется на другой, внешний сервер. Внешних серверов — несколько. Изначально решение было реализовано через скриптинг: скрипт, занимая процесс в операционной системе, читал сообщения, обрабатывал и пересылал по сети. На нескольких мощных железных серверах (по 8 ядер, 16 ГБ ОЗУ) были запущены сотни скриптов (на каждом!), параллельно читающих из SQS, обрабатывающих и пересылающих данные. Просто, надежно, но потребление железа начало надоедать. Затраты на железо постоянно увеличивались.

    На Rust использовали преимущественно стандартные библиотеки и модули из cargo:
    • rusoto_sqs — для работы с Amazon Web Services, никаких вопросов, работает как нужно из коробки
    • rustls — для tls, в т.ч. взаимодействия с TLS-сервисами Google, Apple и использованием клиентских сертификатов
    • signal-hook — для перехвата kill и доведения до конца транзакции по отправке данных
    • futures, tokio-rustls — «ленивая», многопоточная, асинхронная работа с сетевыми TLS-сокетами, аля Node.js
    • clap — парсинг аргументов командной строки
    • serde_json — формирование json, тут ничего интересного


    Без использования «unsafe» блоков и «std::mem::transmute», к сожалению, не обошлось — в стандартной библиотеке не удалось найти инструментов для парсинга бинарных данных в деревья.

    Основной, если можно так его назвать, «затык» случился в компиляции — библиотеки не собирались на CentOS6 по причине «устаревшего ассемблера» в binutils, но на CentOS7 никаких проблем не возникло.

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

    Общее ощущение — очень положительные. Еще бы, вместо нескольких железных серверов (8 ядер, 16 ГБ ОЗУ), задача стала решаться одним процессом (с десятками потоков), кушающим не более 5 ГБ ОЗУ и создающим не очень заметную нагрузку на ядра, при трафике в районе 0.5-1 гигабит.

    Заключение


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

    P.S.:
    * — да, чуть не забыл. Нужно, конечно, рассказать о блоке «unsafe». В этом блоке можно:
    • программировать вещи, недоступные в обычном rust — разыменовывать «сырые» указатели и делать еще несколько потенциально опасных вещей, иногда требующихся в системном программировании. Но все не так страшно:
    • блоки unsafe видны и их либо совсем нет, либо в них сосредоточен ответственный код
    • стандартная библиотека rust тщательно протестирована и значит мы верим, что блоки unsafe в ней работают
    • популярные библиотеки в cargo тоже тщательно тестируются (в rust же встроено все для unit и интеграционного тестирования) и поэтому их можно смело брать и использовать

    Т.е. в блоке «unsafe» нельзя заниматься произвольным развратом, доступным в C — а только cтрого определенными видами опасных активностей. Поэтому можно и нужно спать спокойно :-)
    1С-Битрикс
    118,57
    Компания
    Поделиться публикацией

    Комментарии 169

      0
      Как же удалось отказаться от сборщика мусора?

      А вот так! Память в компиляторе начали представлять как ресурс, «владеть» («ownership») которым может одна и только одна переменная.

      RAII в C++ (к примеру std::unique_ptr)?
        0
        да, я пишу дальше про RAII. Только в Rust логика RAII встроена в сам компилятор и проверяется аффинными типами. Упростили подход и сделали его надежным.
          0
          В STL std::unique_ptr написали такие же программисты, что писали RAII в компиляторе Rust. Где причина повышения надежности?
            +9

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

              0
              Аффинные типы, не?
              +12
              {
                  Foo* a = new Foo{};
                  std::unique_ptr<Foo> p1{a};
                  std::unique_ptr<Foo> p2{a};
                  // Упс, двойное освобождение памяти в конце блока
              }


              В расте это ошибка компиляции.

                0
                Зачем это нужно, если можно создавать объект в std::make_unique?
                  +2

                  Случайно, на стыке старого и нового кода. Или просто по незнанию...


                  Вот, всего месяц назад на ruSO пришлось по-воевать в комментариях за удаление ответа с таким вот кодом:


                   void do_something(shared_ptr<Base> base)
                  {   
                      shared_ptr<Derived> derived( dynamic_cast<Derived*>(base.get()));
                  
                  }

                  Вроде 9000 репутации у отвечавшего, а проблему в этом коде он не видел.

                    +2
                    Пожалуйста:
                    	std::unique_ptr<int> p = std::make_unique<int>(92);
                    	auto fn_once = [p = std::move(p)]() mutable {
                    		std::cout << *p << std::endl;
                    		p.release();
                    	};
                    	fn_once();
                    	fn_once();
                      –2
                      unique_ptr::release() просто так не вызывают
                        +2

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

                          –3
                          а прочитать пару строк кода ниже вашего достоинства?
                          0
                          Но если вызвать эту лямбду только один раз, то всё ок. Ни один современный компилятор не выдаёт предупреждений на такой код, а раст предотвратил бы проблему в аналогичном коде.
                            0
                            Но если вызвать эту лямбду только один раз, то всё ок
                            если вызвать эту лямбду один раз, будет утечка
                          +1

                          Все ручное управление типа get() release() если по честному то приравнивается к ансейф расту, т.к. эти методы нету смысла вызывать в с++ коде. А связь с си в расте тоже ансейф. Другое дело что вы можете сказать что в расте ансейф более явный. Но вообще юзе афтер мув более менее отлавливают анализаторы. Но неприятности как раз приходят в менее тривиальных случаях.

                            +1
                            Можно заменить release() на передачу куда-нибудь через std::move. Суть в том, что корректная операция для однократно вызываемой лямбды оказывается некорректной для многократно вызываемой, но разницы между ними в с++ нет в отличие от раста.
                      +4

                      std::unique_ptr может как указывать на объект, так и быть пустым. В Rust std::boxed::Box всегда что-то содержит.

                    +6

                    Примерно, только за RAII в плюсах нужно следить самому, а тут у вас для этого есть компилятор. Хороший комментарий в тему: https://habr.com/ru/post/415737/#comment_18835083

                    +6
                    Да, он еще молод (2 года как стабилизировался)

                    2 года? Вообще-то расту уже скоро 5 будет, 1 версия вышла в мае 2015. В 2018 создали уже вторую версию, т.н. Rust 2018 Edition, которая фиксит многие огрехи первой версии.

                      0
                      Видимо, очень старые данные у автора… Тот же C# уже давно не прибит гвоздями к Windows, благодаря dotnet core (а в будущем — просто .NET)
                      0
                      Единственные утешения, идущее от Golang:
                      встроенные в язык «green threads» (аля корутины), но они есть уже даже в python
                      компиляция в нативный код, только непонятно, зачем (возможно, чтобы Docker работал) :-)

                      И совсем мутная ситуация с пакетным менеджером. Даже в Node.js и python он есть.


                      Где мутность? Так же сто лет уже есть пакетный менеджер и он так же развивается, как развивается NPM или Cargo.
                        +6
                        Всё ещё не понятно, как этот пакетный менеджер работает. Cargo и npm имеют свои централизованные репозитории, в которых пакеты строго версионируются, а зависимости указываются в одном месте — packages.json/cargo.toml, а построенное дерево зависимостей фиксируется через lock. Чего-то подобного в go не видно
                          0
                          вот именно, я про это и написал в посте
                            0
                            встроенные в язык «green threads» (аля корутины), но они есть уже даже в python


                            Треды суперлегкие. + Приложения с одной и та же логикой реализованные к примеру на node.js, python от 100 до 10 раз потребляют больше ресурсов (оперативная память, утилизация процессора) чем программа на Go

                            Всё ещё не понятно, как этот пакетный менеджер работает.

                            Предполагалось изначально централизованное хранилище пакетов для всех приложений на машине. Да с версиями они изначально «протупили», но уже как год, полтора в механизм управления зависимостями включено версионировании и возможность хранения зависимостей непосредственно локально с приложением.
                              0

                              Видно с введением go modules

                            +12
                            По факту про состояние веб разработки в Rust практически нечего не сказано (состояние тулинга, готовность фреймворков и тд). Возможно название для статьи стоило выбрать другое
                              +9
                              Ну да, какая-то вода с рекламным уклоном, был обещан «опыт быстрой реализации высоконагруженного сетевого проекта», а по факту статья чуть менее чем полностью состоит из кучи довольно спорных тезисов про различные языки без единой строчки той самой «реализации».
                                –5
                                в следующем посте расскажем детально про реализацию, много контента получилось для восприятия, его важно еще переварить
                                  +4

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

                                    +1
                                    ага, спасибо, зашел
                                  +1
                                  добавил детали проекта, да, они полезны, согласен
                                  +2
                                  добавил детали про проект и про тулинг, прошу прощения, упустил, а важно, да
                                    +3

                                    Есть актикс, ну и в общем-то всё. Остальные фреймворки далеко не оптимальны и даже на каком-нибудь го можно получить результат быстрее и лучше.


                                    Основная проблема — веб очень сильно базируется на асинхронности, с которой в языке до сих пор всё ужасно. Для стороннего наблюдателя не особо видно, но писать асинхронный код на расте до сих пор очень больно. Думаю это будет продолжаться еще как минимум полгода. Когда асинк/авейт наконец попадет в стейбл, когда библиотеки стабилизируют асинхронное апи, когда токио выползет на версию 1.0, вот тогда что-то можно будет делать.




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

                                      0
                                      да, пока в Rust с async/await библиотеки стабилизируют (c nigth Rust решили не связываться), полгода-год пройдет, пока на текущих futures 1 версии получилось спокойно взлететь
                                        +6
                                        Когда асинк/авейт наконец попадет в стейбл

                                        Уже же.

                                          +1

                                          Емнип он пока в бете. В стебл в следующий релиз.


                                          Но даже без этого, последние батлы async-std против токио, tokio 0.1 vs 0.3, compat::Backward/Forward туда-сюда, невнятные стримы, апишки которые появляются и исчезают с разницей в пару недель, и дока которая при этом отстает на месяцы (и поэтому неюзабельна полностью).


                                          В общем, пока всё плохо.

                                            +3
                                            Емнип он пока в бете. В стебл в следующий релиз.

                                            В 1.39.0 вышла в Stable, разве нет?

                                                +1

                                                0.2 tokio уже релизнули, остальные библиотеки тоже стабилизируются на 0.2 версии уже.

                                                  +1

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


                                                  В общем, я бы хотел сказать что у раста всё ок, да не могу. Я вот в последнй статье сравнивал хаскель и го, попробовал на расте напилить то же самое, и минут 20 искал как бы мне заавейтить стрим и получить вектор байт. Так и не нашел, потому что дока предлагала методы которых на самом деле у типов не было.

                                                    +4
                                                    0.2 токио вообще по сути был плейграундом для третьей. Не помню ни одной серьезной либы которая поддерживала бы его не в рамках эксперимента. 0.1 и 0.3, причем 0.3 до сих пор сырая.
                                                    tokio 0.2 зарелизилась только совсем недавно. Несколько библиотек в альфе (hyper тот же если не ошибаюсь) таргетились на альфу tokio. Вы с futures крейтом не перепутали? Это именно там были эти backwards/compat.

                                                    заавейтить стрим и получить вектор байт

                                                    Ну вот как раз в tokio который поддерживает async/await пример:
                                                    github.com/tokio-rs/tokio/blob/master/examples/print_each_packet.rs#L89-L102
                                                    До этого тоже самое но на комбинаторах делалось.

                                                    Так и не нашел, потому что дока предлагала методы которых на самом деле у типов не было.
                                                    А версии зависимости у вас совпадали? в некоторых крейтах надо явно активировать features чтобы больше было доступно.

                                                    P.S. Ситуация с асинхронщиной в расте еще совсем недавно была совсем печальная, но сейчас с async/await стабилизацией все очень бурно становится адекватным (сам слежу за асинхронщиной в расте года так с 2016-го).
                                                    А так да, большую часть времени вкатиться в асинхронщину на расте было очень сложно (но поняв базовые концепты все становилось просто). Сейчас с async/await очень скоро все станет гораздо лучше (собственно, уже в какой то мере).
                                                      +1
                                                      Ну вот как раз в tokio который поддерживает async/await пример:
                                                      github.com/tokio-rs/tokio/blob/master/examples/print_each_packet.rs#L89-L102
                                                      До этого тоже самое но на комбинаторах делалось.

                                                      Да нет, мне надо было не сервер написать, а клиент. Я взял гипер, попробовал на нем написать и… И все.


                                                      После того как я пришел к тому, что в футурами миллион ошибок в стиле


                                                      error[E0733]: recursion in an `async fn` requires boxing
                                                        --> src\main.rs:24:43
                                                         |
                                                      24 | async fn get_comments(node: Tree<i32>) -> Tree<Comment> {
                                                         |                                           ^^^^^^^^^^^^^ recursive `async fn`
                                                         |
                                                         = note: a recursive `async fn` must be rewritten to return a boxed `dyn Future`.

                                                      Которые решаются костыликами вроде:


                                                      fn get_comments(node: Tree<i32>) -> Pin<Box<dyn Future<Output = Tree<Comment>>>> {
                                                          Box::pin(async move {
                                                              match node {
                                                                  Tree::Leaf(x) => Tree::Leaf(get_comment(x).await.unwrap()),
                                                                  Tree::Branch(x, left, right) => {
                                                                      let result = get_comment(x).await.unwrap();
                                                                      let left = get_comments(*left).await;
                                                                      let right = get_comments(*right).await;
                                                                      Tree::Branch(result, Box::new(left), Box::new(right))
                                                                  }
                                                              }
                                                          })
                                                      }

                                                      То я забил.


                                                      В итоге так и не понял как жсон получить на hyper = "0.13.0-alpha.2". concat2 нет, TryStreamExt не компилится, Into есть в доке, но в альфе выпилили

                                                        +3
                                                        Ну справедливости hyper всегда хейтили из-за сложности. Если вдруг появится желание поиграться — лучше взять reqwest.
                                                          +1

                                                          Только я не нашел пример как на нем делать асинк-авейт запросы эффективно. Покажите, пожалуйста, если не трудно.


                                                          Когда я его ковырял он поддерживал только синхронные методы. Щас вроде что-то повяилась, но документация как всегда: https://docs.rs/reqwest/0.8.4/reqwest/unstable/async/index.html

                                                            0
                                                            а я в проекте не использовал языковые async/await — а сделал подобное через комбинаторы и стримы и не помню проблем никаких
                                                              +1

                                                              Проблем с ошибками компиляции на три экрана не было?

                                                                0
                                                                Были конечно. Старался обходить в сторону упрощения. Напрягали области видимости, ибо там вот реально все кажется совсем непривычно. Разумеется теоретическая часть перечитана и понята 3 раза как минимум и все возможные ресурсы (в т.ч. по unsafe rust) по rust тоже прорабатывались, кроме самой неполной спецификации языка. Но вот удалось обойти довольно быстро. В результате вся сетевая логика на комбинаторах потоков — функциональная по сути.
                                                                  +1

                                                                  Меня конкретно это смущало. Рефакторить колбасу комбинаторов сложно, особенно то, что написано несколько месяцев назад, т.к. все сигнатуры комбинаторов всё равно не запомнить. Хорошо, что долгожданный async/.await уже стабилизировали.

                                                                    0
                                                                    а типы их автоматически выводит IntelliJ IDEA, можно не запариться, они получаются сложными и громоздкими, но зато их не видно :-)
                                                                      +2

                                                                      Даже когда идея показывает


                                                                      = note: expected type ()
                                                                              found type futures::future::and_then::AndThen<futures::stream::concat::Concat2<hyper::body::body::Body>, futures::future::or_else::OrElse<futures::future::map::Map<futures::future::and_then::AndThen<futures::future::and_then::AndThen<futures::future::map_err::MapErr<futures::future::result_::FutureResult<contract::Update, serde_json::error::Error>, [closure@src\main.rs:139:22: 144:14]>, std::result::Result<(contract::User, std::string::String, i64, i64), http::response::Response<hyper::body::body::Body>>, [closure@src\main.rs:145:23: 162:14]>, futures::future::map_err::MapErr<futures::future::and_then::AndThen<impl futures::future::Future, futures::future::either::Either<futures::future::and_then::AndThen<impl futures::future::Future, futures::future::either::Either<futures::future::then::Then<impl futures::future::Future, futures::future::either::Either<impl futures::future::Future, futures::future::result_::FutureResult<(), telegram_client::TelegramClientError>>, [closure@src\main.rs:211:51: 224:46 telegram_client:_, chat_id:_, text:_]>, futures::future::result_::FutureResult<(), telegram_client::TelegramClientError>>, [closure@src\main.rs:173:90: 230:30 file_id:_, ext:_, user:_, message_id:_, dbs:_, chat_id:_, telegram_client:_]>, futures::future::result_::FutureResult<(), telegram_client::TelegramClientError>>, [closure@src\main.rs:166:31: 235:22 user:_, chat_id:_, message_id:_, telegram_client:_, file_id:_, dbs:_]>, [closure@src\main.rs:236:30: 242:22]>, [closure@src\main.rs:163:23: 243:14 telegram_client:_, dbs:_]>, [closure@src\main.rs:245:18: 248:14]>, std::result::Result<http::response::Response<hyper::body::body::Body>, hyper::error::Error>, fn(http::response::Response<hyper::body::body::Body>) -> std::result::Result<http::response::Response<hyper::body::body::Body>, hyper::error::Error> {std::result::Result<http::response::Response<hyper::body::body::Body>, hyper::error::Error>::Ok}>, [closure@src\main.rs:136:54: 250:6 telegram_client:_, dbs:_]>

                                                                      Сильно удобнее не становится.

                                          +7
                                          Haskell откровенно не взлетел по причине агрессивных «понтов» — чтобы хорошо понять его терминологию, нужно быть, как минимум, кандидатом наук со специализацией в области одной из лидирующих теорий в современной математике: теории категорий.

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

                                            0
                                            я тоже работаю, но зачем же вводить в язык «аппликативные функторы», как только не от чувства избранности?
                                              +5

                                              А что такого сложного в программистком понимании апликативного функтора?


                                              Кроме того интересно, где это берут на работу хаскелистов, которые говорят о неимоверной сложности апликативного функтора?

                                                0
                                                хорошо, скажем «гомоморфизм над категориями» :-) это не издевательство над остальным человечеством?
                                                  +6

                                                  Это относится к программированию на хаскеле примерно никак.

                                                +6

                                                "аппликативный функтор" вещь намного проще, чем например "паттерн мост".

                                                +1

                                                Зашёл сюда оставить (почти) этот комментарий.


                                                По этой же логике растом нельзя пользоваться, не понимая линейную логику.


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

                                                +7

                                                Разве Golang — системный ЯП? Мне кажется, что наоборот — прикладной.

                                                  0
                                                  Java — прикладной
                                                    +6

                                                    Почему Golang в секции системных ЯП?

                                                      –8
                                                      ну так Golang же создавали как замену C и к нему сейчас такое отношение. Он же не высокоуровневый.
                                                        +6

                                                        Я о том, что Golang — скорее альтернатива к скриптовым языкам, нежели к C/C++. Слабо представляется, что на нём можно по-человечески писать драйвера, ОС и другое низкоуровневое ПО.

                                                          0
                                                          согласен
                                                          0
                                                          А я вам чуть не поверил)
                                                            0
                                                            Ну а зачем тогда в Golang напихали строгую типизацию и порезали возможности ООП по мама не балуй, кроме как не сделать из него безопасный C для начинающих. Я так честно думал, пока пост не написал и не почитал комменты :-)
                                                      +2
                                                      Скорее язык, который подходит для тулчейнов и системных утилиток (где раньше правили балом C/C++ и Python), так как очень простой (без замудренных конструкций, так что даже олдскульный хакер C освоит его довольно быстро), переносимый, быстрее Python и при этом со сборкой мусора.
                                                      0
                                                      глобальные переменные нельзя менять (на самом деле можно, но это отдельная продвинутая и не рекомендуемая техника).

                                                      Ничего не продвинутая, очень простая. Либо менять через unsafe, либо использовать синхронизацию (атомарные переменные, мьютексы).

                                                        –1
                                                        только вопрос, зачем? :-)
                                                          +1

                                                          В Tokio так делали с глобальным рантаймом, например (сейчас не знаю что там творится).

                                                            0
                                                            Да нам тоже пришлось unsafe использовать, к сожалению. Добавил в раздел про проект детали.
                                                        +10
                                                        Автор, у вас эффект Даннинга Крюгера в чистом виде.

                                                        == Rust и почему от такой популярный и востребованный.

                                                        зачем здесь эта ссылка, и откуда инфа про популярность и востребованность Rust? Выдаёте свои фантазии за реальность и констатируете их как нечто самоочевидное.

                                                        == встроенные в язык «green threads» (аля корутины), но они есть уже даже в python

                                                        «green threads» в Go ничего общего не имеют с корутинами питона. Вы бы поизучали вопрос прежде, чем делать высказывания космического масштаба и космической же глупости.

                                                        == компиляция в нативный код, только непонятно, зачем (возможно, чтобы Docker работал)

                                                        Похоже, у вас проф. левель недостаточно высок для понимания. Спросите у тех. кому понятно. Вам объяснят на пальцах.

                                                        == совсем мутная ситуация с пакетным менеджером.

                                                        Любая ситуация будет мутной, если смотреть на неё через призму предрассудков и нежелания читать документацию.

                                                        Но вы пишите ещё, ваше мнение очень важно для всех нас.
                                                          +1
                                                          «green threads» в Go ничего общего не имеют с корутинами питона. Вы бы поизучали вопрос прежде, чем делать высказывания космического масштаба и космической же глупости.

                                                          это моя любимая тема, в которой специализируюсь последние несколько лет, увы

                                                          Похоже, у вас проф. левель недостаточно высок для понимания. Спросите у тех. кому понятно. Вам объяснят на пальцах.

                                                          я знаю почему — модно :-)
                                                            0
                                                            это моя любимая тема, в которой специализируюсь последние несколько лет, увы


                                                            Насколько я понимаю, комментатор выше имел в виду что горутины в go реализуют вытесняющую модель многозадачности, а корутины в python — кооперативную.
                                                            В горутинах нам не надо явно писать yield и решать, где приостановить одну горутину и возобновить другую — шедулер языка сам этим занимается.
                                                            Поэтому это две большие разницы, и говорить, что питоновские корутины это аналог горутин и то что они «есть уже даже в python» — некорректно.
                                                              0
                                                              Услышал, но не совсем согласен. Ведь всё это и в Go и в Python — виртуальная многозадачность в пространстве пользователя, выполняемая на N-честных потоках операционной системы. В Java, на честных потоках ее можно реализовать как угодно же, не говоря про Rust.
                                                                0
                                                                Ведь всё это и в Go и в Python — виртуальная многозадачность в пространстве пользователя


                                                                В Go — реальные потоки по числу ядер процессора (можно ограничить в настройках программы и использовать не все ядра, если тебе надо) умножить на «виртуальную» в пределах одного ядра.

                                                                То есть если у вас 16 ядер, то на Go вы можете запустить хоть 10 000 потоков. При этом в действительную параллель вытесняющим образом будут выполнятся только 16, что обеспечаны железом, конечно же.

                                                                Остальные будут переключаться кооперативным образом с этими 16-ю (например, на функциях синхронизации/ввод-вывода).

                                                                +4

                                                                Насколько я знаю, в Go многозадачность очень даже кооперативная — просто yield скрыт под капотом и не светится. Т.е. горутины прерываются только на ожидании асинхронных операций типа чтения-записи сокета или ожидания на канале.

                                                                  +4
                                                                  Что, в принципе, прямой путь к дедлоку, если у нас N ядер, N горутин, и каждая чем-то очень тяжелам занята.

                                                                  Я вообще затрудняюсь сказать кто, кроме эрланга (почему-то вообще не упомянутого в статье, в которой ну очень странно даже про него не обмолвиться) и смолтока, умеет в нормальную вытесняющую многозадачность.
                                                                    +1

                                                                    В вытесняющую многозадачность умеет только шедулер ОС, вот только проблема в том, что он медленный. Поэтому и используются "грин-треды", которые так не умеют, зато быстрее.

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

                                                                      nathanmlong.com/2017/06/concurrency-vs-paralellism тут вот внятно для людей, которые думают, что грин-треды — это не болото, которое повиснет намертво при первом удобном случае.
                                                                        +1

                                                                        Я бы не стал называть подход, когда VM вставляет в код неявные проверки "не пора ли переключиться на планировщик", вытесняющей многозадачностью.
                                                                        Код, выполняющийся в потоке, сам отдаёт управление планировщику, кооперативно. И если например в этом потоке в данный момент выполняется какой-нибудь код на C, ничего не знающий про BEAM, не будет никакого переключения.


                                                                        Да, это позволяет снизить latency, но эти проверки не бесплатны. И разработчик не может сказать VM: "вот тут проверяй, а тут и так сойдёт".


                                                                        думают, что грин-треды — это не болото, которое повиснет намертво при первом удобном случае

                                                                        Я думаю, что это — инструмент, которым надо уметь пользоваться и знать его ограничения. И кстати то, что сделано в BEAM, тоже можно назвать грин-тредами.

                                                                          +2
                                                                          VM вставляет в код неявные проверки «не пора ли переключиться на планировщик», вытесняющей многозадачностью

                                                                          Так и не называйте, VM не вставляет в код никаких проверок (VM вообще ничего в код вставить не может, компилятор может, но и он этого не делает). VM эмулирует процессор для каждого «процесса» (в кавычках, потому что это не системные процессы) и отдает каждому по несколько сотен/тысяч тиков.

                                                                          если например в этом потоке в данный момент выполняется какой-нибудь код на C

                                                                          Угу, половина стандартной библиотеки эрланга — NIFs, и ничего, планируются как миленькие.

                                                                          разработчик не может сказать VM: «вот тут проверяй [...]»

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

                                                                          [green threads] инструмент, которым надо уметь пользоваться [...]

                                                                          Армстронг рассказывал, как он был на какой-то конференции в начале 90-х и все хвастались производительностью, удобством использования и прочими плюшками своих языков. А он подходил ко всем и спрашивал: «А как насчет fault-tolerance?» Потому что рассчитывать на то, что разработчик никогда не сделает ошибку — по меньшей мере наивно.

                                                                          Вот хороший бенчмарк для Phoenix (это заслуга OTP, конечно) — два миллиона соединений на 40core/128gb box. Основное отличие — то, что я могу в миллионе из этих соединений считать факториалы больших чисел, и они ответят клиентам, когда будут готовы, а остальной миллион — этого вообще не заметит.

                                                                          Если это не вытясняющая многозадачность — то я не знаю, что тогда.
                                                                            +1
                                                                            VM не вставляет в код никаких проверок

                                                                            И интерпретатор, и JIT — это часть VM.


                                                                            отдает каждому по несколько сотен/тысяч тиков

                                                                            Если это не интерпретатор с циклом (а я думаю, JIT там есть), то нельзя взять и отдать коду N тиков, можно только сделать этот код таким, что он будет сам их считать. И насколько я знаю по беглому изучению этой темы, в BEAM "тиком" считается вызов функции.


                                                                            Угу, половина стандартной библиотеки эрланга — NIFs, и ничего, планируются как миленькие.

                                                                            Код на C будет считать "тики" только если делать это явно. Наверняка в стандартной библиотеке так и делается. Возьмите любую левую либу и она вам запросто подвесит всё.


                                                                            заблокировал одно из ядер на пять минут, пока он файл до конца не вычитает

                                                                            Ну во-первых, в худшем случае блокируется не ядро CPU, а поток ОС, а во-вторых, освобождать его на время IO как раз таки умеет любой нормальный рантайм с green thread'ами.
                                                                            То, что BEAM при этом ещё и хорошо справляется с тяжёлыми вычислениями на CPU без ущерба для latency — это несомненно очень хорошая фича. Но и без этого можно жить. Про Go не скажу, но например в Kotlin для этого есть средства. Но да, об этом надо подумать разработчику, и да, рано или поздно он подумать об этом забудет. Но везде можно выстрелить себе в ногу, это лишь одна из возможных ошибок, при том не самая фатальная.

                                                                      0

                                                                      А откуда возьмётся именно дедлок? Закончатся тяжелые задачи, и планировщик получит таки управление.

                                                                        +2
                                                                        Настоящего дедлока не будет, конечно. Но когда к вам в queue сыпется 10К сообщений в секунду, подождать минутку — вот вообще не вариант.
                                                                        0

                                                                        К дедлоку вряд ли. К starvation, да, возможно, но это уже совсем другой разговор.


                                                                        Edit: написали выше, писать комменты через два дня после загрузки страницы без их обновления — плохая идея.

                                                                          0

                                                                          Ну да, ну да, не будет дедлока, я игзаджерировал (пардон май френч :)

                                                                +8
                                                                При всей моей любви к Rust, от этой статьи только негативные впечатления.
                                                                В основном из-за того, что автор много рассуждает о языках, о которых знает, судя по всему, только понаслышке.
                                                                Ну или знает не понаслышке, но очень уж поверхностно.
                                                                  +1
                                                                  можете привести конкретный пример, попробую ответить конкретно, подискутируем?
                                                                    +1
                                                                    В нормальном виде история становления ЯП была в книге
                                                                    Кауфман В.Ш. Языки программирования: концепции и принципы
                                                                      –1
                                                                      А моя настольная книга — «Effective Java» Джошуа Блоха. Без воды и правда, чистая правда :-)
                                                                      +2
                                                                      можете привести конкретный пример, попробую ответить конкретно, подискутируем?

                                                                      Ну вот, например.


                                                                      (все же знают, что «npm» в Node.js написан на «rust»?)

                                                                      Насколько я знаю, на расте там только сервис авторизации переписывали.

                                                                        0
                                                                        www.rust-lang.org/production — в левом верхнем углу же
                                                                          0

                                                                          А вы пробовали дальше заголовка читать?

                                                                            –2
                                                                            я да, а Вы?
                                                                              +2

                                                                              А я в первом комментарии написал, что там было на самом деле. Можете перечитать его. Такие ответы вызывают в целом сомнения в компетенции и адекватности автора.

                                                                                +1
                                                                                пофиксил, Вы правы, где-то еще есть сомнения, давайте обсудим, пишите плз.
                                                                              0
                                                                              но Вы правы, я допустил неточность в масштабе применения Rust в npm, пофиксил в посте, прошу прощения
                                                                                +2

                                                                                Не подумайте, что придираюсь, но, честно говоря, формулировка


                                                                                все же знают, что части «npm» в Node.js написаны на «rust»

                                                                                не сильно лучше. Потому что эти "части" это авторизация в регистри. Регистри это важный компонент экосистемы, но непосредственно к Node.JS не относится. Ну и кроме того, npm это не только регистри для Node.JS — но ещё и для огромного количества фронтовых пакетов...


                                                                                Лично мне раст при этом нравится, но как-то чересчур сильно сову на глобус натягиваете — лопнет же. На сайте самого раста, кстати, формулировка абсолютно корректна.


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

                                                                                  0
                                                                                  Ага, услышал, согласен, для большей ясности пофиксил, добавил ссылочку на папер. У нас же нет цели людей запутать. Даем факты и размышляем, а выводы все пусть сами делают.
                                                                      +1

                                                                      Вы знаете, я бы поверил, что кривая вхождения не такая уж сложная, если бы я уже (в латентном режиме с всплесками каждый месяц) уже пол-года не тупил на попытке написать простенький IntoIterable для своей (простой) структуры. Вроде всё понятно, а компилятор не согласен.

                                                                        +2

                                                                        А можно пример? Проблемы с итераторами обычно начинаются только при попытке что-нибудь такое запихнуть в трейт (а-ля trait Foo { fn get_iterator(&self) -> impl Iterator<Item=i32>}).

                                                                          0

                                                                          Мне трудно приводить пример того, от чего у меня мозги скрипят. Вот сейчас попробовал ещё раз написать его. Дано: структура со счётчиком, итерировать от start до end.


                                                                          struct Test {
                                                                              start: i32,
                                                                              end: i32,
                                                                              counter: i32,
                                                                          }
                                                                          
                                                                          impl Test {
                                                                              fn new(start: i32,  end: i32) -> Test {
                                                                                  Test{start:start, end:end, counter:0}
                                                                              }
                                                                          }
                                                                          
                                                                          impl Iterator for Test {
                                                                              fn next(&mut self) -> Option<i32>{
                                                                                  if self.counter < self.end {
                                                                                      self.counter +=1;
                                                                                      return Some(self.counter-1);
                                                                                  }
                                                                                  else {
                                                                                      return None
                                                                                  }
                                                                              }
                                                                          }
                                                                          
                                                                          impl IntoIterator for Test {
                                                                              type Item = i32;
                                                                              type IntoIter = Test;
                                                                              fn into_iter(self) -> Self::IntoIter {
                                                                                  self.counter=0;
                                                                                  self
                                                                              }
                                                                          }
                                                                          
                                                                          fn main() {
                                                                              let x = Test::new(0, 5);
                                                                              for z in x{
                                                                                  println!("{}", z);
                                                                              }
                                                                          }

                                                                          ЧЯДНТ?


                                                                          Компилятор на меня ругается, что
                                                                          conflicting implementations of trait std::iter::IntoIterator for type Test:


                                                                          impl std::iter::IntoIterator for I
                                                                          where I: std::iter::Iterator;

                                                                            +2

                                                                            А, ну тут всё дело, что когда вы реализуете Iterator то IntoIterator реализуется автоматически. реализовать его еще второй раз вы не можете, конечно. Что компилятор вам в ошибке и написал:


                                                                               |
                                                                            25 | impl IntoIterator for Test {
                                                                               | ^^^^^^^^^^^^^^^^^^^^^^^^^^
                                                                               |
                                                                               = note: conflicting implementation in crate `core`:
                                                                                       - impl<I> std::iter::IntoIterator for I
                                                                                         where I: std::iter::Iterator;

                                                                            Вот поправил вашу ошибку: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=750d3a6980b6849334d7bc9a9d9abc07


                                                                            struct Test {
                                                                                start: i32,
                                                                                end: i32,
                                                                                counter: i32,
                                                                            }
                                                                            
                                                                            impl Test {
                                                                                fn new(start: i32,  end: i32) -> Test {
                                                                                    Test{start:start, end:end, counter:0}
                                                                                }
                                                                            }
                                                                            
                                                                            impl Iterator for Test {
                                                                                type Item = i32;
                                                                                fn next(&mut self) -> Option<i32>{
                                                                                    if self.counter < self.end {
                                                                                        self.counter +=1;
                                                                                        return Some(self.counter-1);
                                                                                    }
                                                                                    else {
                                                                                        return None
                                                                                    }
                                                                                }
                                                                            }
                                                                            
                                                                            fn main() {
                                                                                let x = Test::new(0, 5);
                                                                                for z in x{
                                                                                    println!("{}", z);
                                                                                }
                                                                            }
                                                                              0

                                                                              А, ок, спасибо большое. Но это не отменяет специальной головной боли при размышлении про IterType и Type в impl IntoIterator. Это точно не "простая кривая вхождения".

                                                                                +9

                                                                                Ну я лично не вижу тут каких-то проблем. С тем же успехом можно было бы попробовать в джаве отнаследоваться от двух разных классов и словить ошибку что ты уже наследуешься от друого. Одного уровня ошибки.

                                                                          +2
                                                                          Это оттого, что вы давно не были в Питере. Вот известная сенён-парочка из JB живёт и работает в Питере, и они для Rust написали аж целый плагин для Idea. А жизнь на Кипре не располагает к серьёзным компьютерным наукам.
                                                                            +5
                                                                            Сенён-парочка? Известная? Я что-то пропустил?
                                                                              0
                                                                              я тоже гуглить начал, стало прям интересно :-)
                                                                          +1
                                                                          Еще, пока видимо не очень прямо удобным местом является малое количество сред разработки. Неплохо ведет себя IntelliJ с модулем для rust, но иногда не справляется с подсказкой сложных типов. Однако можно писать и в Notepad++ — умный компилятор с гарантиями предупредит вас о ошибках в коде.


                                                                          Visual Studio Code с расширениями rust-lang и CodeLLDB является прекрасной средой разработки.
                                                                            +6

                                                                            VSCode работает через RLS который просто ужасен. Есть превосходная альтернатива от товарища Кладова, но она наверное даже не преальфа до сих пор.


                                                                            Я лично пользуюсь идеей с плагином, 90% нужного функционала в ней присутствует. Иногда подтупливает + не все рефакторинги есть, да и со сложными макросами ей тяжело, но в остальном всё ок.

                                                                              +2

                                                                              Может и преальфа и его нужно компилить вручную, но работает он намного лучше чем rls

                                                                                +1
                                                                                1) Чем же RLS ужасен?
                                                                                2) Идея не умеет дебажить Rust. Умеет CLion, но он в отличии от идеи платен.
                                                                                  +3
                                                                                  1. тем что он тормозит и отваливается. У кладова в целом достаточно написано в разделе мотивации для его проекта
                                                                                  2. а) Фишка в том что раст обычно дебажить и не надо. Пару раз когда мне это нужно было это как ни странно был сегфолтящийся плюсовый код и clion мне в любом случае нужен был чтобы понять что там происходит
                                                                                  3. б) Если бы у меня не было clion я бы все равно писал в идее, а дебажился бы раз в полгода в vscode когда совсем припрет.
                                                                              +6
                                                                              Отличная статья.

                                                                              Но вызвало улыбку что о таком замечательном Rust написали ребята из 1С-Битрикс. Для меня 1С и Битрикс являются кака бы полной противоположностью Rust.

                                                                              Может через пару лет 1С язык заменит Rust?
                                                                              Это было бы очень неожиданно и интересно посмотреть!
                                                                                0
                                                                                Спасибо! :-)
                                                                                +2
                                                                                внезапно вы узнаете, что используете монадические вычисления в вышеупомянутых цепочках, которые разумно обрабатывают ошибки, однако тут нет умничания с терминами из теории категорий

                                                                                Это просто к 1.0 не успели скрестить HKT и Borrow Checker, а теперь вот сидим с ad-hoc решениями.

                                                                                  +1

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

                                                                                    +1

                                                                                    Проблема в том, что в текущих типах эта проблема вообще не решается. Коротко от лодочника: https://twitter.com/withoutboats/status/1027702531361857536?s=21


                                                                                    Так что есть мнение что чтобы это так работало нужно было прям вообще сильно иначе всё делать. Ну или жить с "почти" монадами, как впрочем и хаскель живет с "почти" категорией Hask.

                                                                                      +1
                                                                                      Ну или жить с "почти" монадами, как впрочем и хаскель живет с "почти" категорией Hask.

                                                                                      Это выпилить хотят (по крайней мере, лично я хочу).

                                                                                    +1

                                                                                    Почему язык назвали "ржавчиной"??

                                                                                      +3

                                                                                      https://stackoverflow.com/questions/16494822/why-is-it-called-rust


                                                                                      Если коротко, то он называется в честь ржавчинного грибка (а не оксида железа) с очень интересными свойствами, которые восхищают одного из разработчиков языка.

                                                                                      +3
                                                                                      высоконагруженные проекты на Java работают с регулярными паузами по несколько секунд, которые нельзя убрать никак

                                                                                      Надо чтобы руки ну очень сильно росли из задницы, чтобы получить паузы по нескольку секунд.
                                                                                        +10
                                                                                        Обратите внимание от лица какой компании публикация.
                                                                                          0
                                                                                          Вот у меня есть такой проект на Apache Mahout с несколькими матрицами 10 на 10 миллионов с хэш-мапами с линейным пробингом и чего я только с GC разных версий не делал, регулярно какая-то муть со сборкой мусора, иногда вызывающая такие вот неприятные паузы. На других проектах прям вот STW нет, но замедления регулярные. Еще отлично тормозит на несколько секунд регулярно любимая нами среда IntelliJ IDEA ;-)
                                                                                            +2
                                                                                            Странно упоминать библиотеку предназначенную для batch-processing'а в контексте обсуждения web-разработки. Или вы пытаетесь её использовать прямо в процессе обработки http-запроса? IDEA же тормозит не столько из-за Java, сколько из-за семантического анализа. Некоторые, написанные на C, компиляторы на той же задаче впадают в такую задумчивость, что можно смело на перекур отправляться.
                                                                                              0
                                                                                              Смотри, пост для текущих веб-разработчиков, пишущих на PHP в основном, чтобы задумались об использовании Rust для решения текущих задач.
                                                                                          +5
                                                                                          это просто лютый треш, столько бреда в одном месте я давно не читал
                                                                                            –4
                                                                                            так и задумано :-)
                                                                                              0
                                                                                              Полностью согласен. Статья содержит кучу противоречивого бреда и хочется оспорить буквально каждый абзац по знакомым мне технологиям. Но воздержусь
                                                                                              В целом статья никак не оправдывает свой кричащий заголовок.
                                                                                                0
                                                                                                Можно, пожалуйста, конкретнее, я готов пояснить неясные в посте моменты.
                                                                                              –1
                                                                                              Написано так словно автор собрал и более грамотно описал всё что я думаю о расте ) Хотя я его ещё не собрался пощупать, но выглядит всё именно так.
                                                                                                0
                                                                                                старался, да, надеюсь пригодится!
                                                                                                +2
                                                                                                python/java/c++/nodejs/php — все плохие) один Rust хороши )) какое-то такое впечатление от статьи…
                                                                                                  –2
                                                                                                  нет, выводов не делаю, просто факты и размышления
                                                                                                  +3

                                                                                                  Странно, что вы не упомянули нативную поддержку работы с AST из коробки — немного криво и косо, но авторы языка подумали (!) об этом, что явно выгодно выделяет Rust из когорты других языков, как по-настоящему пригодный к метапрограммированию.

                                                                                                    0
                                                                                                    да, вы правы!
                                                                                                    –1
                                                                                                    Как соединили несоединимое и впихнули невпикуемое, что не удавалось сделать за последние 50 лет. Оказалось, возможно, если...
                                                                                                    … запретить все потенциально небезопасные конструкции.

                                                                                                    Выразимость эффективной логики программы в терминах безопасных конструкций никто никогда не гарантировал
                                                                                                      +2
                                                                                                      Выразимость эффективной логики программы в терминах безопасных конструкций никто никогда не гарантировал

                                                                                                      Мне кажется выразительность ником образом не теряется. Теряется всегда производительность и возможность делать какие-то трюки которые опять же чаще всего нужны для максимальной производительности или максимально оптимального использования ресурсов (памяти, шины, сети и проч).
                                                                                                        –4
                                                                                                        Мне кажется выразительность ником образом не теряется
                                                                                                        как же не теряется то, если я не могу например иметь константную и мутирующую ссылку на объект одновременно, делать неявные преобразования типов или кидать исключения?

                                                                                                        Спору нет, этот функционал пожертвован ради безопасности. Выгода такого размена… неоднозначна.
                                                                                                          +1
                                                                                                          кидать исключения

                                                                                                          Это с безопасностью никак не связано. Есть паника — несколько урезанное подобие исключений. Причём "неполноценность" паники просто из-за дизайна языка, который подталкивает к другому способу обработки ошибок.


                                                                                                          делать неявные преобразования типов

                                                                                                          И их отсутствие — это просто замечательно.

                                                                                                            –2
                                                                                                            вы вообще не поняли посыл. Выйдите на секунду из манежа пресловутой безопасности и посмотрите шире. Речь про выразительность — совершенно иной аспект языка программирования. Ну вот нельзя в расте написать двусвязный список. Почему? Потому что борроучеккер ради безопасности. Ну нельзя в расте кинуть исключение. Почему? Ради унификации механизмов обработки ошибок. Нет в расте неявных преобразований типов. Почему? Потому что за вас решили что это вам не нужно. Каждый такой компромисс в дизайне языка жертвует выразительностью в угоду безопасности и унифицированности. Хорошо это или нет — полностью субъективно. Выразительность падает — факт.

                                                                                                            Вон тут ниже очередной растовитянин (который, емнип, не написал ни строчки прода на расте), высмеивал гошника за комментарий в духе «потому что не нужно». Вот в расте кучи всего нет… «потому что не нужно».
                                                                                                              0

                                                                                                              Несчастные функциональщики без двусвязных списков всю жизнь живут, и ничё.


                                                                                                              Не потому, что не нужно. А потому что мутабельность нужно выжигать каленым железом.

                                                                                                                0
                                                                                                                Не потому, что не нужно. А потому что мутабельность нужно выжигать каленым железом...
                                                                                                                … потому что не нужно?

                                                                                                                Функционым яп иммутабельность нужна так же, как полиморфизм объектно-ориентированным. Мы же говорим про императивный rust
                                                                                                                  0

                                                                                                                  Нет :)


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

                                                                                                                    0
                                                                                                                    иммутабельность с точки зрения производительности тоже не безгрешна. Например, необходимо копированть контейнер ради изменения одного его элемента, или вводить хитрые COW иммутабельные структуры данных (про них есть много докладов), в которых тоже есть где теряться производительности.
                                                                                                                +2

                                                                                                                Я вот не помню когда последний раз мне вообще был нужен двусвязный список. А вот невозможность выразить отношение владения в моём C# мешает мне регулярно.


                                                                                                                Так что, с моей точки зрения, выразительность у Rust как раз отличная.

                                                                                                                  0
                                                                                                                  Я вот не помню когда последний раз мне вообще был нужен двусвязный список.

                                                                                                                  Я вот со скриптовых языков пытался ворваться в rust (возможно, что в этом и проблема, и такие вещи решаются как-то иначе в rust), но невозможность построить банальное дерево с отношениями вида дети-родитель сломала мой уютный мирок, и покоробило мой не готовый к такому повороту событий мозг. Я не говорю хорошо это или плохо — у меня не достаточно опыта в таких вопросах.


                                                                                                                  Субъективно: мне это доставило огромные неудобства.
                                                                                                                  И объективно: уверен, что я не один такой.


                                                                                                                  Погуглив, я нашел решение с ареной, но уж чего-чего, а выразительности оно не прибавляет от слова совсем.


                                                                                                                  Еще есть решение с unsafe. Но в растономиконе, прямо сказано "ататата использовать unsafe, если вы его до конца не понимаете".


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


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


                                                                                                                  Всё выливается в довольно забавную сценку в моей голове:


                                                                                                                  — Как мне сделать двусвязный список?
                                                                                                                  — Вам не нужен двусвязный список — говоря это, собеседник
                                                                                                                  делает джедайский пас рукой.
                                                                                                                  — Не вам решать, что мне нужно — невозмутимо продолжаю я.
                                                                                                                  Мой собеседник становится огорчен и фальшиво суетлив, как агент по продажам который вдруг понял, что потенциальный клиент никогда не станет реальным.
                                                                                                                  — Тогда вам rust вам не поможет — с этими словами он утыкается в монитор, полностью потеряв ко мне всякий интерес, и что-то вдумчиво вводит в терминал.
                                                                                                                  — Вот и я о том же…
                                                                                                                  И я огорчен, что мой собеседник не смог мне помочь. а у самого в голове проскакивает мысль: Так зачем ссорится, если обо всём уже договорились? Ну — значит не моё.
                                                                                                                    +2

                                                                                                                    Банальное дерево с отношениями "дети-родитель" банально же строится через Rc/Weak, и двусвязный список ему не нужен.


                                                                                                                    Проблемы в Rust начинаются в тот момент, когда вы пытаетесь сделать чуть оптимальнее за счёт отказа от Rc. Но в данном случае это не важно, поскольку даже с Rc получается куда эффективнее чем в Javascript.




                                                                                                                    Кстати, вы вот эти реализации смотрели?


                                                                                                                    https://doc.rust-lang.org/std/collections/struct.LinkedList.html
                                                                                                                    https://docs.rs/trees/0.2.1/trees/

                                                                                                                      0
                                                                                                                      Но в данном случае это не важно, поскольку даже с Rc получается куда эффективнее чем в Javascript.
                                                                                                                      а если хочется максимально эффективно?
                                                                                                                      doc.rust-lang.org/std/collections/struct.LinkedList.html
                                                                                                                      емнип двусвязный список в расте реализован через индексы нод арена-аллокатора c O(n) ассимптотикой вставки. Тогда двусвязный список действительно не нужен, ведь теряется его единственное преимущество над вектором.
                                                                                                                        +2
                                                                                                                        а если хочется максимально эффективно?

                                                                                                                        То надо придумать правильную абстракцию и реализовать её с помощью unsafe.


                                                                                                                        емнип двусвязный список в расте реализован через индексы нод арена-аллокатора

                                                                                                                        Да нет, он использует обычный unsafe код с указателями.

                                                                                                                        0
                                                                                                                        Банальное дерево с отношениями "дети-родитель" банально же строится через Rc/Weak, и двусвязный список ему не нужен

                                                                                                                        Я уже сейчас и не вспомню, но на слабых ссылках там тоже какие-то проблемы.


                                                                                                                        На момент моих попыток "врыва" (начало 2017) trees еще не было.


                                                                                                                        Что касается двусвязного списка, то он тут действительно не при чем. Просто проблема мне показалась схожей (циклические ссылки, насколько я могу понять). Только по этому и зацепился. Хотя даже больше потому, что когда я пришел в сообщество со своими вопросами, мне прям слово в слово сказали: Я вот не помню когда последний раз мне вообще был нужен двусвязный список были нужны такие деревья. Прям дежавю ощутил.

                                                                                                                          0
                                                                                                                          Просто проблема мне показалась схожей (циклические ссылки, насколько я могу понять).

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


                                                                                                                          У дерева же родитель владеет детьми (ну или, в некоторых задачах, дети владеют родителем — но такое дерево делается ещё проще), а потому через Rc/Weak дерево реализуется тривиально.

                                                                                                                    +3
                                                                                                                    вы вообще не поняли посыл.

                                                                                                                    Возможно, но верно и обратное.


                                                                                                                    Нет (привычных) исключений — отлично, обработка ошибок на Result мне больше понравилась, когда распробовал.
                                                                                                                    Нет неявных преобразований? Я бы решил точно так же. И этот аргумент можно повернуть назад: если для своих типов в С++ ещё можно explicit использовать, то для встроенных приходится мириться с той логикой, которая зашита в язык. И да, которую выбрали за меня.


                                                                                                                    Я вообще-то долгое время на С++ писал и отдельные вещи в расте поначалу вызывали раздражение. И сказать хочу, что и там и там разработчики (или комитет) решают за нас. И там и там можно написать proposal/RFC и попытаться повлиять на дизайн языка. И, наконец, и там и там есть свои компромиссы. Мне далеко не всё нравится в расте, но в С++ таких вещей (для меня) больше.


                                                                                                                    Ну и в плане выразительности тягаться с языками с GC дело неблагодарное.


                                                                                                                    Вот в расте кучи всего нет… «потому что не нужно».

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


                                                                                                                    Ну и в официальных обсуждениях никто не говорит "не нужно" — есть аргументация и она, как правило, убедительная.

                                                                                                                      0
                                                                                                                      Нет (привычных) исключений — отлично, обработка ошибок на Result мне больше понравилась, когда распробовал.
                                                                                                                      это субъективное мнение. Я пишу только факты. Например, обработка ошибок через исключения производительнее для позитивного сценария.
                                                                                                                      если для своих типов в С++ ещё можно explicit использовать, то для встроенных приходится мириться с той логикой, которая зашита в язык. И да, которую выбрали за меня.
                                                                                                                      любой встроенный тип с++ можно обернуть, а вот с борроучеккером раста уже не ничего не сделаешь. Плюс, неявный апкаст без потери точности (а-ля u32->u64 или f32->f64) вести к вычислительным ошибкам не может — почему бы не разрешить хотя бы его? Почему бы не разрешить введение неявного каста для пользовательских типов?
                                                                                                                      Ну и в плане выразительности тягаться с языками с GC дело неблагодарное.
                                                                                                                      Выразительность языка с GC не является недостижимой для системного. Например, можно сравнить те же плюсы с go.
                                                                                                                      Ну и в официальных обсуждениях никто не говорит «не нужно» — есть аргументация и она, как правило, убедительная.
                                                                                                                      «не нужно потому что ...»?
                                                                                                                        +1
                                                                                                                        это субъективное мнение. Я пишу только факты. Например, обработка ошибок через исключения производительнее для позитивного сценария.

                                                                                                                        Да, но то, что сигнатура с Result даёт больше информации — это тоже факт. А вот если мы начнём считать насколько быстрее или насколько это дополнительная информация полезна, то быстро скатимся к субъективному.


                                                                                                                        любой встроенный тип с++ можно обернуть

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


                                                                                                                        Плюс, неявный апкаст без потери точности (а-ля u32->u64 или f32->f64) вести к вычислительным ошибкам не может — почему бы не разрешить хотя бы его?

                                                                                                                        Полагаю, что ради явности.


                                                                                                                        Почему бы не разрешить введение неявного каста для пользовательских типов?

                                                                                                                        "Полунеявное" преобразование, кстати, есть — через From/Into трейты. То есть, можно написать функцию так, что она принимает не конкретный тип, а всё, что может быть в него преобразовано. В коде, который вызывает эту функцию, никаких преобразований видно не будет.


                                                                                                                        Например, можно сравнить те же плюсы с go.

                                                                                                                        Смешно. И даже тут можно попробовать подобрать примеры, где Go будет выигрышнее смотреться. А может взять какой-нибудь язык поинтереснее и тогда разница будет ещё заметнее.


                                                                                                                        «не нужно потому что ...»?

                                                                                                                        Скорее что-то вроде "не очень понятно как это сделать не поломав Х" или "сначала надо стабилизировать Y".

                                                                                                              +5

                                                                                                              Возможность разделять программу на "опасные" и "безопасные" куски очень полезна. Те же плюсы плохи не тем, что там наворотить можно, а тем, что нужно 100% быть аккуратным.


                                                                                                              В расте можно быть аккуратным только если unsafe написал.

                                                                                                                0

                                                                                                                Но это не мешает Box быть более эффективным, нежели unique_ptr

                                                                                                                  0
                                                                                                                  бенчмарк в студию
                                                                                                                –4
                                                                                                                И совсем мутная ситуация с пакетным менеджером. Даже в Node.js и python он есть.


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

                                                                                                                В Go прямо с github и т.п. можешь выкачивать командой go get github.com/чё-надо.
                                                                                                                До этого выполните команду go mod init — и за версиями будет следить Go сам, не допуская того, чтобы пакет со сменившимся API вас всё сломал.
                                                                                                                  +6
                                                                                                                  Протому что не нужно.

                                                                                                                  Ну как всегда, всё чего нет объявляется нинужным.


                                                                                                                  Зелен виноград как он есть.

                                                                                                                    –3
                                                                                                                    Ну как всегда, всё чего нет объявляется нинужным.

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

                                                                                                                      +1

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


                                                                                                                      Но ваш вариант "вы ничего не поняли, это нинужна" — совсем другой.

                                                                                                                        –1
                                                                                                                        Комментарий умышленно написан в форме стёба над теми, кто уже делает неверные выводы, еще не понимая сути вещей.

                                                                                                                        В статье есть над чем посмеяться.

                                                                                                                        Категоричные выводы по поводу того, что действительности не соответствует. Для многих языков.
                                                                                                                          +1

                                                                                                                          В статье полно слабых мест, на которые уже обратили внимание выше. Можно было бы вполне конструктивно найти недостатки в том, что написали.


                                                                                                                          А не брать сомнительный тезис и возводить его в ранг абсолюта.

                                                                                                                            –1
                                                                                                                            Неужели не понятно, что я написал пост для фана, чтобы посмеяться еще раз над тем, что творится и сделать следующий шаг в верном направлении :-)
                                                                                                                    +2
                                                                                                                    Спасибо за статью. Как с языка сняли, почти все тоже самое у меня в голове крутится об этих языках и техниках вокруг них, хотя опыта поменьше чем у Вас. Кажется, что это очевидные вещи должны быть для других разработчиков в пользу выбора Rust. От себя добавлю некоторые мысли для других: представьте себе серьезный военный конфликт (не глобальный, но очень серьезный), Системы должны быстро оценивать ситуацию, как можно быстрее реагировать, уметь это делать параллельно, т.е. оценивать ситуацию по разным параметрам и нельзя допустить, чтобы программа упала в самый ответственный момент, должна быть надежной, без утечек, без гонок за ресурс, и т.д. Этот большой проект нужно сделать быстро, время на тестирование минимальное, повторюсь, система должна быть очень быстрой, параллельной, надежной, ресурсы НЕ безграничны (это вам не amazon)… Какой язык выберите, уважаемые? Rust. Вопрос закрыт.
                                                                                                                      0
                                                                                                                      Спасибо!
                                                                                                                        –1
                                                                                                                        а что, rustc + llvm уже сертифицированы для использования военными?
                                                                                                                          –3
                                                                                                                          Уважаемые выберут, скорее всего, что-нибудь вроде SPARK/Ada, но никак не растик.

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

                                                                                                                        Самое читаемое