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

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

Гриша, расскажи, как вы в Evrone пришли к использованию Rust? 

13 лет назад, когда Evrone только начинался, мы специализировались на full stack Ruby решениях. Но с годами мы начали расширять палитру того, что предлагаем клиентам. Вначале у нас выделилось направление фронтенд - это React, Vue.js. Затем к нам приполз Python, как альтернатива Ruby, потому что многие клиенты выбирают стек технологий под уже существующие команды и процессы. 

И уже несколько лет мы используем Rust как комплементарную технологию, которая позволяет точечно ускорять наши решения на Python и Ruby в 10, а иногда и 100 раз. 

Теперь, когда к нам приходит бизнес и говорит: «Ну вы же знаете, что Ruby нетороплив, наша система уже 100 000 запросов в секунду, нам надо потратить еще вагон денег и переписать ее на Java. Да?» Мы такие: «Нет! Мы выделяем вот эти три точки, которые у вас под нагрузкой, и наши ребята переписывают их на Rust. У вас всё начинает работать быстро, надежно и при этом поддерживаемо, а бизнес фичи продолжают фигачиться с той же страшной скоростью». 

И мы в Evrone используем Rust именно для таких решений: берем наши проекты на Ruby и Python, ищем в них места с высокой нагрузкой. Когда клиент хочет, чтобы мы переписали это на каком-то системном языке, мы предлагаем либо Rust, либо Go, в зависимости от команды клиента, его бизнеса. И нам очень нравится результат, потому что можно использовать лучшее от обоих миров: где бизнес-логика и не надо высоких скоростей, много строчек кода, и надо, чтобы разработчики могли прийти и быстро разобраться, чтобы фигачить бизнес-фичи, мы используем Ruby или Python. А для тех немногих задач, где десятки и сотни тысяч запросов долбятся в сервера, мы используем Rust либо Go.

Rust иногда называют «убийцей С++», согласен ли ты с такой оценкой?

Скорее да, чем нет. Rust идеально занял ту нишу, которую раньше занимали С и C++.

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

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

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

А если мы сравним Rust и Go? Какие преимущества есть у каждого из них и какие недостатки? 

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

Отличия в том, насколько язык заставляет разработчика заботиться о памяти. Go - умеренно, он находится где-то между C++ и Python. Он с одной стороны позволяет разработчику писать низкоуровневый код, а с другой стороны там есть garbage collector, и нет необходимости заниматься такими вещами, как семантика владения: кто выделил память, кто владеет памятью и кто освободит память. 

Что это значит с точки зрения программиста, который пишет код? Go по синтаксису языка гораздо проще, чем Rust. Но у этой простоты есть своя цена. Между Go и операционной системой есть некоторая прослойка - go runtime. Это не такая прослойка, как в Python или Ruby,где используется виртуальная машина. Go очень быстрый, компилируется в машинный код, но прослойка по управлению памятью все равно останется. Go делает код надежным за счет garbage collector, так же как Java и C#. 

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

Go создан для того, чтобы писать маленькие и быстрые программы, при этом надежность никогда не была частью синтаксиса, экосистемы, это отдается на откуп программистам. Чтобы программисту на Go писать надежный код, ему нужно обучиться мастерству написания надежного кода на любом языке программирования. А это, на секундочку, 10-15 лет опыта. Понятно, что Go возник не так давно, и опытный программист на С++ будет писать надежный код на Go и обучать этому джунов. Так как Go  - максимально простой в освоении язык, то джуны под руководством такого лида тоже начинают писать надежный код, и у компании всё хорошо. 

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

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

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

Ты упоминал про сложность изучения Rust. Возникает вопрос, как вы в Evrone обучаете ему сотрудников? Или вы берете готовых специалистов? Мы сталкивались с тем, что разработчики изучали Rust как дополнительный язык программирования, при этом не могли понять, где его применить и как найти работу, потому что во многих компаниях его еще нет. 

Интересный вопрос. Естественным образом сложилось, что у нас корни в Ruby. Рубистов у нас много. Специфика Ruby - это язык для бизнеса. Его называют “станком для программиста”: разработчик приходит, становится за этот “станок” и начинает пилить бизнес-фичи. Это очень приятный в использовании станок, за которым удобно работать. 

Ruby, так же как и Rust, - сложный. Это не Python, который считается лучшим языком для обучения новичков. Ruby - это профессиональный, очень богатый синтаксисом и семантикой язык. На Ruby крайне мало джунов. Он почти никогда не бывает первым языком программирования, на него переходят разработчики, которые уже несколько лет писали на чем-то и теперь хотят делать бизнес-автоматизацию, backend, erp, crm, криптобиржи, админки и тд. Они выбирают Ruby, изучают его и получают удовольствие от работы. Как правило, это компетентные разработчики, которые знают несколько языков.   

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

А как вы решаете проблему code review? Если ребята сами учились, наверняка должен быть кто-то, кто может все проверить? 

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

Что делать компании, когда у нее только один Rust-разработчик? Кто будет ревьюить его код? Мы в Evrone не стесняемся обращаться к сторонним экспертам. Я один из организаторов сообщества Moscow Python, делаю Ruby Russia и другие конференции. И когда нам нужно собеседовать, а все наши лиды заняты, мы обращаемся к сообществу, платим деньги, и топовые российские Python, Ruby или JS эксперты помогают нам собеседовать новых бойцов. Это вполне себе общепринятая практика в бизнесе. 

Как найти такого человека, который тебе поможет? Для этого у вас в команде должен быть компетентный DevRel. Уверен, это могут делать и компании, которые только начинают использовать Rust - приходить в сообщество, общаться с кем-нибудь (например, с нами) понимать, кто из сообщества может прийти и помочь с Rust разработкой, code review и тд. 

Что ты думаешь про будущее Rust? Одни говорят, что это очень перспективный стек, а другие - что это просто модная игрушка. 

Это они 25 лет назад не пытались на плюсах исключения ловить. 

Если вы посмотрите на современный софт - это же баг на баге. Создаваемый во всем мире софт очень ненадежный. 

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

Я не умею предсказывать будущее. Возможно, Google или Microsoft создадут новый язык, который съест Rust, Go, Python, Ruby, JS и настанет светлое будущее моноязыка. 

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

Сколько разработчику из другого стэка нужно времени, чтобы освоить Rust на сколько-нибудь приемлемом уровне?

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

Растбук очень хорош. У опытного разработчика (не джуна, а middle+), который уже много лет пишет на другом языке программирования, месяца три займет вдумчивое чтение книги, плюс еще столько же уйдёт на практику. В общем, полгода - это разумный срок для middle+, который занимается учебой один-два часа каждый день. С одной стороны, значительные инвестиции, с другой стороны это вам не японский выучить, там 3-5 лет на базовый уровень. 

Один-два часа это очень много, на самом деле. Вы скажете: «да что там, я сериалы один-два часа в день смотрю». Но изучать что-то новое, читать реально сложную книгу, практиковаться со сложным языком программирования - это высокая стрессовая нагрузка на нашу систему подкрепления, простите мне мой нейрофизиологический жаргон. Уже через 5-10 минут система подкрепления и все наше естество начнет вопить, что нет, я не хочу этим заниматься, у меня чугунная голова, я хочу смотреть сериалы или программировать на том, что уже знаю.  Так что один-два часа в день - это надо прям собраться и выдержать. 

Но если мы чего-то хотим достичь, приходится инвестировать личное время, страдать и становиться лучше.


Конференция RustCon Russia пройдёт 3 декабря в Москве. Если вы дочитали до этого места - то вот вам подарок: промокод evrone даёт скидку 10% на покупку билета :)


Комментарии (161)


  1. KanuTaH
    01.12.2021 19:40
    +12

    Кто хочет, чтобы аварийно завершился сервис, считающий рейтинги в образовательном проекте? Чтобы рейтинги у всех обнулились или пропали? Или чтобы аварийно завершилась система, которая занимается аутентификацией, и чтобы пользователи потеряли возможно логиниться в систему?

    Я знаю, кто - кто-то, кто вставил unwrap() в темные глубины 100500-того крейта, который используется в таком сервисе. /s


    1. Alexei_987
      01.12.2021 20:12
      +16

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


      1. Sergey_zx
        01.12.2021 20:23
        +3

        А посмотреть дамп и стек что то мешает?


        1. Alexei_987
          01.12.2021 20:24
          +9

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


          1. fougasse
            01.12.2021 20:29
            +1

            Для такого, обычно, делается core dump.

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

            Вот от спецэффектов и падений где-то в glibc на ARM уже сложнее защититься, но жизнь она такая.


            1. 0xd34df00d
              01.12.2021 23:44
              +26

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


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


              1. fougasse
                02.12.2021 12:14
                +1

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

                Санитайзеры фактически бесполезны, соглашусь.


        1. Gordon01
          02.12.2021 11:26

          unwrap on None в неосновном потоке не приводит к завершению программы.

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


          1. DarkEld3r
            02.12.2021 13:41

            unwrap on None в неосновном потоке не приводит к завершению программы.

            Так-то и в основном потоке панику можно перехватить.


            Паника безопасна.

            Да, но с оговорками. Если нет ансейфа, то UB действительно не случится, но и тогда программа может оказаться в разломанном состоянии. Если код написан грамотно, то такого не случится, но в нормальном коде и анврапов особо не будет.


    1. vkni
      01.12.2021 21:05
      +1

      Вот, кстати, реально не понимаю этой любви к unwrap() у наших домашних Рустовцев. Оно же серьёзно убивает обработку ошибок.


      1. warlock13
        01.12.2021 21:42

        Нормально unwrap() используется в таких местах, где только написавший этот unwrap() знает, как надо обработать возникшую ошибку.


      1. Mr_Mih
        01.12.2021 21:53
        -4

        Да и сама концепция использования Result/Option сбивает с толку, но кто мешает вместо unwrap() возвращать ошибку из функции?


        1. vkni
          01.12.2021 22:36
          +12

          Концепция использования Result/Option в тех языках, где они появились, достаточно проста - вы тем или иным образом прямо там, где они появились, обрабатываете ошибки.

          Либо это может быть просто передача ошибки дальше, с "быстрым выходом" из цепи преобразований (т.н. "монады"), либо просто раскрытие и обработка ошибки на месте, либо замена на "правильное" значение. Но ни в Haskell, ни в Ocaml народ не использует unwrap() как "заметание ошибки под ковёр".


      1. Medeyko
        02.12.2021 09:09
        +2

        Так ведь unwrap, по-моему, должен бы использоваться только в ситуациях, когда уместен quick'n'dirty подход - типа прототипирования или написания небольших программок. Насколько я понимаю, идея с unwrap'ом примерно в том же духе, что и с unsafe'ом - локализация возможных проблем, так что перед релизом библиотеки или серьёзной программыможно достаточно легко прошерстить код, и убрать unwrap'ы (или проверить, что они не могут срабатывать) и проверить правильность unsafe'ов. Ну или хотя бы при возникновении ошибки достаточно быстро её найти.


    1. DarkEld3r
      01.12.2021 22:46
      +4

      Я знаю, кто — кто-то, кто вставил unwrap() в темные глубины 100500-того крейта, который используется в таком сервисе. /s

      Ну если так говорить, то в чём разница с вызовом std::abort внутри сторонней библиотеки?.. По большому счёту, "внезапный unwrap" — это баг. Да, язык от всех проблем не защищает, увы.


      1. vkni
        01.12.2021 23:01
        +1

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

        "Можно вывести девушку из деревни..." - кмк, это проблема культурного типа. С другой стороны, возможно язык не поддерживает простоту написания длинных монадических цепочек с Result? Ну как в Хаскеле это просто блок do.


        1. DarkEld3r
          02.12.2021 00:31

          С другой стороны, возможно язык не поддерживает простоту написания длинных монадических цепочек с Result? Ну как в Хаскеле это просто блок do.

          Не думаю, что дело в этом. В расте принято обмазываться вопросиками и код в таком стиле писать не сложно, но это не всегда спасает. Вот предположим, что у нас есть вектор в котором гарантированно не меньше пяти элементов. Можно везде писать get(2).and_then(…), но не хочется. Особенно если функция без этого могла бы возвращать просто значение, а не Result/Option. Ответственный человек завернёт такой код в абстракцию и напишет комментарий (и тест заодно). Принципиально эту проблему решают разве что зависимые типы, ну или я чего-то не знаю про хаскель.


          1. 0xd34df00d
            02.12.2021 01:08
            +1

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


          1. vkni
            02.12.2021 01:56

            Значит культурное. Просто во всяких Хаскелях культура такая, что там обязательно обзовут аналог unwrap именем "unsafeUnwrap". Кмк.

            В Окамлах, конечно, есть свои приколы насчёт non-tailrec map, но это вроде бы уже почти побеждено (см TMC).


            1. vkni
              02.12.2021 02:53

              Не прав, есть fromJust, который аналог unwrap. Видимо его меньше используют.

              Тем не менее, кошерный способ - это maybe https://hackage.haskell.org/package/base-4.16.0.0/docs/Data-Maybe.html#v:maybe

              В Ocaml тоже есть get, аналогично кошерный способ - value.


              1. 0xd34df00d
                02.12.2021 03:11
                +1

                Аналогом unwrap было бы игнорирующее ошибку fromRight, но, к счастью, fromRight определено как b -> Either a b -> b.


                fromMaybe (как и нетотальные head/tail/etc) — наследие 30-летней истории языка, в общем-то.


                1. vkni
                  02.12.2021 06:17

                  Мне очень нравятся Хаскельные zipWith, take, drop, которые с одной стороны корректно обрабатывают все ситуации, а с другой стороны не выбрасывают исключений в отличие от OCaml'овских map2, iter2.


            1. DarkEld3r
              02.12.2021 09:12
              +2

              Просто во всяких Хаскелях культура такая, что там обязательно обзовут аналог unwrap именем "unsafeUnwrap". Кмк.

              Тут два момента. Во первых, соглашения — если привыкнуть, то за unwrap/expect глаз цепляется не хуже, чем за unsafe префикс. В этом плане хуже, что паниковать могут другие методы, например, доступ по индексу через квадратные скобки. Это тоже можно порешать, но уже костылём (линтом Clippy). Во вторых, ансейф в расте обозначает конкретное подмножество проблем, а не просто "потенциально опасная операция". Паниковать или допускать утечки — "безопасно".


          1. domix32
            02.12.2021 11:41

            Кстати, ваш пример уже завернут в chunk_exact.


        1. amishaa
          02.12.2021 00:31
          +1

          Некоторый аналог (достаточно близкий) - это ?-нотация.

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


          1. vkni
            02.12.2021 01:59

            Значит мой опыт Руста слишком мал. Я, в общем, и не претендую.

            Кстати, по рассказам Not-Kernel-Guy'а внутри ntkrnlos используется вот тот же "монадический" подход в стиле С. То есть, делается макрос

            #define CHECKED_RUN( f, errorMSG)

            if( ....()) {
            logError...
            goto END;
            }

            и в каждой функции делается метка END.


  1. Alexufo
    01.12.2021 20:10
    +6

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


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


    1. codefun
      01.12.2021 21:54
      +3

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


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


    1. grigoryvp
      01.12.2021 23:30

      Объясняет. Когнитом Анохина и схемы внимания Грациано


      1. Alexufo
        02.12.2021 01:15
        +1

        А как объясняет? Не хотелось бы пересматривать такое количество роликов.


        1. grigoryvp
          02.12.2021 13:54

          Кинул в личку выжимку.


          1. Alexufo
            04.12.2021 04:16

            ага) Ответил там же)


  1. Shmaiser
    01.12.2021 21:17
    +3

    То есть если бы сразу написали на Java/C# то и не было бы проблем с переписыванием? Плюс найти работников на эти языки и связанные с ними фреймворками проще.


    1. Paskin
      02.12.2021 13:28
      -2

      Были бы проблемы с нужностью продукта к моменту окончания написания.


  1. MFilonen2
    01.12.2021 21:49
    -7

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


    1. snuk182
      01.12.2021 22:55
      +8

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


    1. 0xd34df00d
      01.12.2021 23:46
      +4

      А что с этим путём? В оперднях не особо используется, да, а там, где предметная область сложная, и инструмент для неё подходит — вполне.


      1. vkni
        02.12.2021 02:57

        Haskell вроде бы из-за алгебраических типов, развитого pattern matching и Show почти везде очень хорошо подходит для опердней. И статической типизации.


  1. alhimik45
    01.12.2021 21:50
    +5

    Лет 5 назад первый раз попытался вкатиться в раст. Не особо получилось, казалось что borrow checker многого не понимает и приходится писать очень странный код чтобы он понял что тут всё хорошо.
    Недавно попробовл ещё раз. На этот раз от языка только приятные впечатления, borrow checker не мешает, если какие-то претензии у него имеются, то только по делу. А вот экосистема немного огорчила.
    Сейчас в языке целых два актуальных асинхронных рантайма, не совсем совместимых друг с другом, и крейты обычно поддерживают только один из них.
    Ещё мне не особо зашло логирование. После .NET грустно смотреть на отсутствие какого-нибудь LogContext который бы позволял в определенном скоупе добавлять в лог данные. Как пример, мне не удалось придумать как в Tide или Warp на уровне middleware создать некоторый request-id, чтобы различать логи от параллельных запросов.
    Пока определенно продолжу его изучать, но думаю это пока ещё не язык где можно на расслабоне собрать что-то полезное без костылей.


    1. SergeiMinaev
      01.12.2021 22:22

      Сейчас в языке целых два актуальных асинхронных рантайма

      Три - ещё smol достоен внимания.


      1. alhimik45
        01.12.2021 22:26
        +2

        Разве async-std не использует как раз smol? https://github.com/async-rs/async-std/pull/757


    1. Mr_Mih
      01.12.2021 22:25
      +2

      А то что в расте два строковых типа вас не напрягает?


      1. alhimik45
        01.12.2021 22:31
        +1

        Нет, там более менее понятно почему так было сделано, да и работа с ними проблем особо не вызывает


      1. Izaron
        02.12.2021 00:14
        +4

        Вы имеете в виду String и &str? Это практически аналоги std::string и std::string_view соответственно из плюсов, с такими же сферами применения. У этих двух типов сферы использования пересекаются, но есть случаи когда конкретный тип нужнее. В C++ и в Rust оба типа используются постоянно.


      1. enabokov
        02.12.2021 02:54
        +5

        Больше чем 2 ;)


        1. Pancir
          02.12.2021 03:19
          +7

          Справедливости ради, здесь все преувеличенно.
          Смело все что с u8, можно отсюда убрать, сюрприз-сюрприз, но стринги состоят из байт, фундаментально, в любом языке, даже в питоне приходится иногда туда сюда делать конвертацию. Path по сути к стринге тоже отношение прямого не имеет, по этой логике все, что работает со стрингой можно сюда добавить, а &'static str это частный случай &str. Фундаментально остается 3 вида стринги с их слайсами (slice), 2 из которых нужны для вполне конкретных вещей (внешнего взаимодействия (API)).


      1. GreWeMa
        02.12.2021 10:30
        +2

        Их гораздо больше двух.

        И все из них - по делу.


    1. mayorovp
      01.12.2021 22:46

      Как пример, мне не удалось придумать как в Tide [...] на уровне middleware создать некоторый request-id

      А чем не устраивают методы ext/ext_mut/set_ext?


      1. alhimik45
        01.12.2021 22:59

        Эх, я не до конца выразил мысль: создать так, чтобы во все вызовы логирования внутри реквеста он автоматически подставлялся без явного пробрасывания. Что-то подобное можно сделать с помощью slog_scope, но тогда необходимо использовать логгер из скоупа из-за чего теряется возможность делать локальные логгеры через slog::Logger::new c дополнительной инфой.
        В общем проблема в том, что держателем дополнительных данных для логирования является только сам логгер, нет дополнительной штуки (как например в serilog) в которую можно напихать инфы и её залогирует любой логгер который будет вызван в её контексте.


        1. mayorovp
          01.12.2021 23:06
          +1

          То есть, по сути, вы жалуетесь на отсутствие аналога AsyncLocal?


          1. alhimik45
            01.12.2021 23:34

            Не столько AsyncLocal, это обходится тем же инструментированием future как в slog_scope, сколько на недостаточную [для меня] гибкость существующих логгеров.


        1. Qwertovsky
          02.12.2021 20:48
          +3

          Использую https://docs.rs/log-mdc/0.1.0/log_mdc/ вместе с log4rs. Возможно это то, что вам нужно. Добавляю что-то в контекст


          use log_mdc;
          
          log_mdc::insert("userId", login);

          В паттерне прописываю {X(userId)(anon)}. И любой вызов логгера пишет эту переменную или anon.


          1. alhimik45
            02.12.2021 21:13

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


    1. DarkEld3r
      01.12.2021 22:53

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

      Мне кажется, что токио всё-таки победил, но сильно спорить не буду. Хотя графики активности говорят сами за себя:
      https://github.com/tokio-rs/tokio/graphs/contributors
      https://github.com/async-rs/async-std/graphs/contributors


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


      После .NET грустно смотреть на отсутствие какого-нибудь LogContext который бы позволял в определенном скоупе добавлять в лог данные.

      Кажется, что tracing решает эту задачу.


      1. alhimik45
        01.12.2021 23:28

        Насколько я понял ситуацию с асинхронностью, то токио существовал ещё до того как в rust завезли async/await, оттого и популярность. А async-std вроде как теперь стандарт, и я как новичек хотел бы ориентировался на него, а у него с поддержкой в библиотекой как раз грустно.


        Кажется, что tracing решает эту задачу.

        Ну в той же мере, что и slog_scope. Я выше уже написал что хочу чего-то более гибкого, но видимо я зажрался в этом своём дотнете :)


        1. DarkEld3r
          02.12.2021 00:45
          +1

          А async-std вроде как теперь стандарт

          Почему? Только из-за наличия "std" в названии? Может, конечно, я варюсь в пузыре и сильно заблуждаюсь, но больше похоже на то, что эта библиотека умерла. Из того о чём знаю: растовая libp2p изначально использовала как async-std, но и там сделали поддержку токио. О случаях когда токио не поддерживается не слышал.


          Ну и я привёл ссылки не количество коммитов, то есть "активность разработки", а не число скачиваний. Хотя и по этому параметру у async-std всё не очень (1,321,707 недавних скачиваний против 7,417,479 у токио).


          Ну в той же мере, что и slog_scope.

          Всё-таки чуть-чуть эргономичнее из-за атрибутов и прочих мелких удобств. (:


        1. Fenex
          02.12.2021 09:03
          +2

          > А async-std вроде как теперь стандарт

          Наоборот, де-факто токио всех победил, это сейчас стандарт.


          1. alhimik45
            02.12.2021 12:32

            Окей, буду знать, спасибо


  1. solarcore
    01.12.2021 21:58
    +9

    Можно пример того, какую конкретно нишу занял Rust, которую раньше занимал C++?


    1. grigoryvp
      01.12.2021 23:34
      +3

      Тут я, похоже, неправильно выбрал слова и во время интервью, и во время редактуры. Не "занял" как "выпинал из этой ниши всех". А "занял" как "решает задачи этой ниши лучше чем те, кто ее занимает". Наверное, надо использовать слово "вписался" :) Ядра операционных систем, инфраструктурные штуки, системные утилиты, то что называется "toolchain".


      1. solarcore
        01.12.2021 23:51
        +1

        Я понял вас, спасибо


  1. fougasse
    01.12.2021 22:08
    +10

    По собственному опыту, в проекте с ~ 1M LOC C++(userspace) + C(kernel) на backend и ~500k LOC в толстом клиенте с Java FX, переход на Kotlin 3 года назад произошел намного более гладко и незаметно, чем попытки интегрировать Rust в сушествующий продукт.

    Да, у JVM огромное преимущество изначально, но даже, если не рассматривать «сложность»(скорее необычность) парадигмы написания нового кода на Rust, интеграция с существцющим отбирает невероятно много ресурсов.

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

    В других случаях(опять же по личному впечатлению) интеграция — это боль и мучения.

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


    1. snuk182
      01.12.2021 22:49
      +1

      Java в принципе так себе склеивается с чем-либо еще. У меня на прошлом проекте перепробовали кучу вариантов, включая JNI, MQ, и даже CORBA. В итоге победил микросервис.


      1. vkni
        01.12.2021 22:59
        +3

        Кто-то писал интересную фразу о том, что склеивать два языка с разными сборщиками мусора - мучение. А вот языки без сборщиков мусора склеиваются с чем угодно значительно легче (через тот же C ABI).


        1. snuk182
          02.12.2021 00:30
          +1

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


          1. vkni
            02.12.2021 01:51
            +1

            Именно с С, т.к. он без сборщика мусора. А вот склеить Питон и С# уже вроде бы непросто.


            1. snuk182
              02.12.2021 23:51

              Даже в пределах одной ВМ (Java + Kotlin, Java + Scala) проскакивают спецэффекты. А пытаться дружить разными рантаймами - и вовсе особый вид страдания.


        1. fougasse
          02.12.2021 09:13
          +1

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

          Да, я в курсе, что нужно быть готовым и всё-такое, но в реальности не получается.


  1. DrAndyHunter
    01.12.2021 22:13

    До конца не понял, зачем патчить кастомными заплатками ruby на rust и выпускать это в продакшен. Кем после вас будет это дело все поддерживаться и эксплуатироваться? Например, если потребуется перенести на другой сервер. Или вы форкнули интерпретатор целиком?


    1. grigoryvp
      01.12.2021 23:34
      +6

      Зачем патчить? Микросервис делаем на расте и всех делов ????


    1. shikhalev
      02.12.2021 06:09
      +4

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


  1. powerman
    01.12.2021 22:33
    +13

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


    1. grigoryvp
      01.12.2021 23:35

      Мне самому очень интересно, как Rust будет махаться с Go в перспективе ближайших 10 лет. Я уже запасся попкорном))


      1. QeqReh
        03.12.2021 06:14

        как Rust будет махаться с Go в перспективе ближайших 10 лет.

        Ни как не будут. Разные сферы применения.


  1. Tuwogaka
    01.12.2021 23:35
    -5

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


    1. Pancir
      02.12.2021 00:53

      Читаю и думаю, что-то очень знакомое, ах, ну да.
      https://habr.com/ru/post/585052/#comment_23622300

      Это ваша коровы и вы ее доите.


      1. Tuwogaka
        03.12.2021 08:39
        -4

        Спасибо за Ваше внимание к моему творчеству, вот уж не ожидал. Особенно от такого сообщества как Хабр, где всё делается чтобы слышать только то, что нравится. Ззрослые разумные люди, ага…

        Возвращаясь к корове… Это не корова точно, скорее осёл которого я действительно и пока безуспешно пытаюсь простимулировать. Увы, против глупости сами боги бороться бессильны.

        Чисто чтобы не говорили, что не предупреждал. Как правило все детские по началу принимаемые за успешные шалости разбиваются именно о перемены. Что и даёт нам, например, опенсорс как смесь ещё не отложенного с уже устаревшим. И Rust уже не тянет второй и мелкой перемены. Печалька. Можете плотнее затыкать себе уши работая над моей кармой.

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


        1. Medeyko
          03.12.2021 17:54
          +2

          Честно говоря, я не очень понимаю Вашу претензию. Где именно документация на сайте rust-lang.org не приведена в соответствие с текущей версией? Насколько я вижу, документация и по языку, и по стандартной библиотеке вполне соответствует самой свежей стабильной версии 1.57.

          Судя по Вашим предыдущим комментариям, Вы имеете в виду то, что в книге "The Rust Programming Language" написано "This version of the text assumes you’re using Rust 1.55 or later with edition="2018" in Cargo.toml of all projects to use Rust 2018 Edition idioms." Но в этом лично я не вижу проблемы: различия между версией 1.55 и 1.57 невелики, и для книги, предназначенной для первоначального обучения, несущественны. Разве что можно об этом одним предложением во введении упомянуть, чтобы это было понятно всем.

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


          1. Tuwogaka
            04.12.2021 14:51
            -2

            Ага, «различия между версией 1.55 и 1.57 невелики» - конечно, ведь они принципиальны, как и должны быть по самому понятию изданий. Если нарушена обратная совместимость, то есть шанс по старому изданию научиться тому, что по новому работать не будет.

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

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


            1. Medeyko
              04.12.2021 20:07
              +3

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

              1. Введён раздробленный захват в замыканиях. Речь о том, что теперь компилятор не будет ругаться, когда разные компоненты структуры захватываются по отдельности. Несохранение совместимости здесь для новичка вообще незаметно - оно заключается только в тонкостях последовательности вызова Drop, и проявляется только для самописных реализаций Drop с внешними эффектами - вряд ли я ошибусь, если скажу, что 99,9% программирующих на Rust'е никогда с этой тонкостью не столкнутся.

              2. При прямом использовании array.into_iter() итерирование теперь происходит по значениям, а не по ссылкам. Причём при нормальном непрямом использовании всё осталось по-прежнему, for &e in &[1, 2, 3] работает как работало. Сделано же изменение для того, чтобы появившаяся в версии 1.53 возможность писать for e in [1, 2, 3] реализовывалась без временного обходного манёвра. На практике такое прямое использование встретить очень сложно, а для новичка оно уж точно неактуально.

              3. $_:pat в macro_rules стало понимать |. Сломало это только некоторые макросы, которые принимали в качестве параметра выражения с | и обрабатывали их сами. Не думаю, что таких макросов было сколько-нибудь заметное количество - не знаю, для каких ситуаций такая логика полезна. Это не то что новичку, это и старичку-то неактуально.

              4. Новый механизм разрешения фич для пакетов, введённый в версии 1.51, стал использоваться по умолчанию. Различия только для пакетов, которые подключаются несколько раз с разными параметрами. Раньше искался общий вариант фич, теперь пакет может компилироваться несколько раз с разным набором фич. Тонкости, очень далёкие от новичка.

              5. Типажи TryInto, TryFrom and FromIterator включили в прелюдию. Соответственно, в некоторых случаях реализации в своих типажах методов их этих типажей (т.е. try_into, try_from и from_iter) может быть неоднозначность того, какую реализацию использовать. Опять-таки, по-моему, крайне маловероятно, что этим столкнётся не то что новичок, а и старичок.

              6. Теперь все макросы panic имеют тот же логичный формат параметров, что и макрос print - раньше был зоопарк с разными их реализациями в std и core . В книге, по-моему, ничего менять не придётся, но это, пожалуй, единственное изменение, с которым мог столкнуться новичок.

              7. Зарезервирован на будущее синтаксис вида any_identifier#, any_identifier"..." и any_identifier'...'- некоторые макросы могли воспринимать это как раздельные токены, а теперь будут единые. Пока ни для чего конкретного не используется, и не уверен, что вообще хоть где-нибудь на практике это изменение хоть что-то сломало.

              8. Предупреждения компилятора bare_trait_objects and ellipsis_inclusive_range_patternsприобрели статус ошибок. В книге ничего менять не надо, затронутые конструкции являются нерекомендуемыми уже в редакции 2018, принципиального значения те конструкции не имеют: в первом случае надо добавить dyn, во втором - заменить ... на ..=. Новичок столкнуться с этим шансов не имеет, если он не полез изучать модуль, написанный в 2015-й редакции.

              Вот и всё. Как видите, изменения микроскопические. В документации по языку они отражены, конечно, везде, где нужно. Что касается книги, то, действительно, поменять 1.55 на 1.57, а 2018 на 2021, а также сказать о трёх, а не двух существующих редакциях не сложно, но я вижу несколько моментов:

              1. Это ни на что не влияет в плане получения читателем знаний.

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

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

              С другой стороны, я, конечно, соглашусь, что это было бы красиво, если бы сразу после выхода версии 1.56, буквально в тот же день, в The Rust Book внесли бы упоминание редакции 2021. Но именно что красиво, не более того, по-моему.


              1. Tuwogaka
                06.12.2021 10:58
                -1

                Я не просто прочитал внимательно, я изначально предполагал, что так может быть написано. Поэтому пытался предотвратить объясняя, что если изменения невелики, то и исправить просто, а если просто - то и сделать это нужно обязательно. Когда простые вещи не делаются, это признак того, что команда уже вступила на типичный для опенсорса путь самоуничтожения. Почему Линукс витрина и исключение? Да просто Линус не чурался наорать.

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

                2. Ну да, ещё одно ограничение снято, а как раз новичок смотрит что происходит, а не рад что абы как сработало и ладно. Пиши ссылку - какого дьявола? Вот реакция новичка.

                3. Старичку - не актуально, а новичку с незамыленным глазом - интересно как раз при первом чтении с вероятностью процентов 30. Хотя если он узнаёт неверно, то обнаружит это, скорее всего, не скоро или никогда.

                4. То же самое. Практически не очень важный, но практически очевидный вопрос.

                5. Это да, не интересно.

                6. Тут просто напомню из маркетинга - чтобы не покупать потребителю достаточно одной причины.

                7. Действительно неинтересно.

                8. И это тоже.

                В документации действительно отражено, но начинают с книги, а не с документации. Если реально придерживаться Вашего мнения, то книгу следует вообще удалить.

                1. Я уже писал про глупость этого «не влияет». Такое выясняется после прочтения книги с лежащим рядом листочком с изменениями.

                2. Ну вот, не сделано потому, что кому-то лень. Что ещё не сделано по той же причине? Кто заставил вместо документации использовать авторский текст? И когда уже написано, что авторский текст был изменён сообществом, участие авторов точно не требуется.

                3. Смешно. Версия у облучающегося та, что на play.rust-lang.org. Или та, что скачивают с первой страницы rust-lang.

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


                1. Medeyko
                  07.12.2021 01:00
                  +4

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

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


  1. korsetlr473
    01.12.2021 23:43
    -2

    где то кроме блокчейнов применяется? как не посмотришь 99% всего на блокчейнах на нём


    1. DirectoriX
      02.12.2021 00:56

      Если мы говорим про отдельные проекты — мне сразу в голову приходят Sequoia-PGP, rav1e (который, правда, наполовину ассемблером оптимизирован) и ripgrep (в VS Code используется для поиска по файлам).


    1. enabokov
      02.12.2021 02:47

      Известные компании перепиливают критичные места с JS на Rust для компиляции в WASM.


  1. creker
    02.12.2021 00:05
    +3

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

    Довольно странное утверждение. Что тут является неожиданной ошибкой? В моем понимании касательно плюсов это либо необработанное исключение, что не так страшно. Либо повреждение памяти. И тут никаким местом плюсы не являются адекватным языком. Он не предоставляет никаких средств для работы с ними в отличии от сравниваемых Go и Rust. Надежные системы можно конечно на всем строить, но таки С++ далеко не лучший вариант для этого. Я бы даже сказал один из худших. По крайней мере в отсутствии внешних тулз, которые бы обуздали его ненадежность.

    А что до разделения, как раз в Go оно наиболее правильно и сделано. Это либо паника, которая кладет приложение (можно конечно поймать, но редко когда это делают) предсказуемым образом и выводит лог, по которому практически всегда можно сразу понять причину. Это реально неожиданная ошибка получается, читай, баг в коде. Все остальное это ожидаемые ошибки, которые должны быть обработаны хоть как-то. И в этом плане Rust и Go очень близки - они оба делят ошибки на эти две категории, и ведут они себя примерно одинаково.

    Касательно необходимости раста. Я до сих пор не могу найти причин его изучать, кроме как желания быть на волне хайпа с остальными. Язык интересный, но гарантии и от того высокая цена разработки на нем оставляют очень маленький набор кейсов, когда он реально был бы полезен и смотрелся лучше рядом с конкурентами вроде Go, C# или даже Python. Один только взгляд на синтаксис отбивает желание с ним связываться. Я вижу постепенное его проникновение в ядра разных ОС и тут у меня вопросов никаких. Наиболее перспективным видится выдавливание С/С++ из различных областей, где эти языки предоставляют базовые важные компоненты вроде библиотек шифрования, драйверов всего и вся, вэб серверов, JIT и рантаймов других языков. Все то, что составляет основу всех систем и все то, что постоянно страдает от небезопасности этих языков.


    1. Izaron
      02.12.2021 00:25
      -4

      Было бы неплохо "из коробки" иметь возможность часть нового кода писать на Rust и связывать его с плюсами. C++ может использовать C, в котором другая система линковки, через extern "C". Почему бы не поддержать в компиляторах extern "Rust" ? Это дало бы буст к юзабельности Rust в реальных проектах и все вышеназванные проекты можно было бы перевезти постепенно.

      В той же паре Java/Kotlin можно совершенно бесшовно вызывать код одного языка из другого языка.


      1. DarkEld3r
        02.12.2021 00:52
        +4

        Почему бы не поддержать в компиляторах extern "Rust"

        А как это должно выглядеть?


        Во первых, не представляю вызов дженериков из другого языка (аналогично с плюсовыми шаблонами). Сюда же хитрости с бороу чекером — это получается компилятор условной джавы должен будет проверять, что растовые функции правильно вызываются? Если всё это вырезать, то останется примерно тот же extern "C", что и в расте сделать можно.


        Во вторых, у раста нет стабильного ABI.


      1. creker
        02.12.2021 12:30

        Всем вышеназванным проектам запросто хватит C ABI. Вы же понимаете, почему Java и Kotlin вызываются бесшовно - у них общий рантайм.


      1. domix32
        02.12.2021 13:12

        Раст поддерживает C ABI, а других FFI пока толком и не придумали, хотя уже несколько лет ведутся разговоры о более безопасных вариантах, да и спецификации на FFI фактически нет. Есть конечно еще интерфейсы для wasm, но оно не слишком хорошо генерализируется для интеграции в другие языки, хотя вполне можно компилять что угодно в wasm и подключать уже wasm либы (rust, python, c#, c++ и java насколько мне известно имеют соответствующие либы).
        А java есть JNI, но оно вроде по сути та же обертка поверх C ABI. Так что не вижу каких-то принципиальных вещей которые могли бы улучшить ситуацию. Разве что кто-то таки возьмется описывать спеку на FFI.


        1. domix32
          02.12.2021 13:42
          +1

          Есть еще вот такое https://github.com/dtolnay/cxx


    1. newpavlov
      02.12.2021 01:51
      +1

      >гарантии и от того высокая цена разработки

      Цена "высока" только если измерять отрезок хренак-хренак и в продакшн. Если же брать в сумме с последующим дебагингом и развитием получившегося творения, то ситуация уже далеко не так однозначна и начинает зависеть от множества факторов. Плюс цена разработки падает с увеличением опыта. Да, в первые месяцы Rust будет значительно менее продуктивен по сравнению с языками вроде Go изученными с нуля на том же сроке, но освоившись продуктивность (по моему опыту и отзывам других людей) становится сравнимой. Более того, становится страшно писать что-то крупное на языках без гарантий и возможностей которые даёт Rust.

      >Один только взгляд на синтаксис отбивает желание с ним связываться.

      Честно говоря, мне это нытьё про синтаксис совершенно непонятно. Да, есть неудачные моменты вроде турборыбы и дублирования ограничений в `impl` блоках, но в основной массе претензии к синтаксису выглядят либо как синдром утёнка, либо жалобами на то что компилятору нужно сообщать больше информации в сигнатурах (отсюда код для незнакомых с языком выглядит излишне "нагруженным"). Информационная плотность сигнатур, после привыкания наоборот становится плюсом.


      1. lain8dono
        02.12.2021 10:05
        +2

        неудачные моменты вроде турборыбы

        https://turbo.fish/


    1. grigoryvp
      02.12.2021 13:57

      Семь лет назад я подробно об этом рассказал на PiterPy: https://youtu.be/hzVECcMI8ys


      1. creker
        02.12.2021 15:32
        +3

        И что, простите, я должен почерпнуть из этого рассказа? Да, я пролистал только слайды, но по ним и так понятно - это ликбез в самые основы обработки ошибок. Вы коснулись все тех же двух классов ошибок - ожидаемые и нет. Одни мы ловим и проверяем, другие кладут программу. Сетевые ошибки, отсутствующий файл, разное окружение - это все базовые вещи, которые все и всегда должны проверять. Здесь не может быть неожиданных ошибок. Если самую обычную ошибку вроде FileNotFound не ожидали, то это ошибка в коде, который эту ошибку не проверил и что-то с ней не сделал.

        Go воспитал целое поколение программистов, для которых проверка всех и каждой ошибок на каждом вызове функции это норма. Проверяется тип, содержимое ошибок, разворачивается стек ошибок, если используется errors.Wrap и ему подобные. Что-то обрабатывается, что-то игнорируется, что-то возвращается пользователю, что-то логируется, а что-то просто кладет приложение. В чем здесь проявляется печальность обработки ошибок Go (и Rust заодно, раз он использует примерно тот же подход) и в чем здесь C++ хоть чем-то лучше будет? Напротив, я считают исключения довольно противной вещью, которая не способствует понятному и надежному коду, и в наше время это тема очень противоречивая, от чего современные языки и пошли другим путем. Swift тут тоже в тему будет.


        1. grigoryvp
          02.12.2021 15:41
          +2

          Без обид, но я не хочу пересказывать свой доклад текстом. Печальность обработки ошибок Go в том, что нет простого способа по err определить весь диапазон возможных значений и выбрать что мы хотим отнести к классу "ожидаемых" и проверять, а что хотим отнести к классу "неожиданных" и падать. В Rust эта фундаментальная проблема решается типом Result и двумя семантиками языка: Enum'ами и pattern matching. В каждой точке обработки ошибок мы получаем от IDE autocompletion вообще всего, что туда может прийти. Это позволяет принимать решения, а не делать вид что мы, как вы метко написали, "что-то обрабатываем, а что-то нет".


          1. noize
            02.12.2021 17:56
            +1

            >> что нет простого способа по err определить весь диапазон возможных значений
            уже давно есть -- https://pkg.go.dev/errors
            можно заворачивать ошибки в ошибки и раскручивать их в вызываемом коде.


            1. grigoryvp
              03.12.2021 10:14
              +4

              Поясните, пожалуйста. Вот я как разработчик микросервиса получил err от какой-нибудь библиотеки взаимодействия с амазоном и хочу посмотреть на весь ассортимент ошибок, чтобы выбрать те, которые в данной точке программы являются для меня ожидаемыми и на которые я могу осмысленно реагировать. Например все, что связано с сеткой. Остальные ошибки - амазона, библиотеки, файлов итд для меня неожиданные, их я обрабатывать не хочу и хочу падать со стектрейосом, чтобы микросервис позвал взрослых. Как /errors мне поможет написать этот if? Как я выберу те значения, с которыми должен сравнить err?


          1. creker
            04.12.2021 16:30
            -3

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

            Мне не нужен список всех ошибок. Я сам знаю, какие ошибки меня интересуют на каждом вызове. Если я используют амазон и обращаюсь к S3, то я отлично понимаю, что мне нужны сетевые ошибки, ошибки доступа к бакету, отсутствие объекта. Как правило, все библиотеки возвращают свой кастомный тип ошибки, который упомянутым errors и приведением типов я могу вытащить и получить нужную мне информацию. Если доков не достаточно, то я, внезапно, просто тупо читаю код. В этом смысле у Go огромное преимущество перед тем же C++ - какая бы ни была сложная библиотека, я запросто могу читать ее код и с большим успехом его понимать. Я не могу такого сказать о многих других языках. Все. Проблемы такой просто нет, вообще. Все остальные ошибки для меня являются неожиданными, и они могу идти в логи, возвращаться юзеру, приводить к панике и т.д. и т.п. Меня не интересует их содержание. Если я не знаю о них и не предполагаю их обработку, то мне о них и знать нет никакого смысла. Вот например, запросили объект, а бакета вообще физически нет. Чего мне толку знать это - что я с этой информацией сделаю? Эта ошибка вернется пользователю, и пусть он с ней сам разбирается.


        1. 0xd34df00d
          02.12.2021 19:05
          +3

          В чем здесь проявляется печальность обработки ошибок Go

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


          1. QeqReh
            03.12.2021 06:23
            +1

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

            Как ты проверишь на уровне компилятора правильность входных данных?

            Вот есть у тебя time.Parse, он кидает ошибку. Как ты предлагаешь её проверять на уровне компилятора?

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

            В Go так же можно описывать ошибки через типы (пишу код по памяти):

            type errFoo struct{
             s string
            }
            func (e errFoo) Error() string {
              return e.s
            }
            
            func main() {
              if err := foo(); err != nil {
                switch err {
                  case errFoo:
                  log.Println("foo error!")
                  default:
                  log.Println("Unknown error.")
                }
              }
            }

            https://go.dev/blog/error-handling-and-go

            Типы можно ещё проще задать (для очень простых ошибок):

            var fooError = errors.New("foo error!")


            1. 0xd34df00d
              03.12.2021 07:11
              +2

              Я бы хотел на уровне компилятора проверять правильность обработки ошибок.


              Как в go выразить «эта функция может быть вызвана из любой функции, в списке возвращаемых ошибок которой есть MyError»?


              1. QeqReh
                03.12.2021 07:57

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

                var errFooType = errors.New("foo error")
                
                func foo() errFooType {
                  return errFooType
                }
                
                func bar() errFooType {
                  return foo()
                }


                1. 0xd34df00d
                  03.12.2021 08:13
                  +1

                  Не, я немного о другом. Например, в своём любимом хаскеле я могу написать функцию, которая может вернуть ошибку FooError, например, следующим образом:


                  foo :: (MonadError e m, HasError FooError e) => ...

                  и спокойно её вызвать в функции, могущей ошибиться с FooError либо BarError:


                  foo :: (MonadError e m, HasError FooError e, HasError BarError e) => ...


                  1. QeqReh
                    03.12.2021 13:12
                    -2

                    Если типов ошибок больше, ты будешь их все перечислять?


                    1. 0xd34df00d
                      04.12.2021 04:02
                      +4

                      Нет, мне надо перечислять не все, а только релевантные.


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


          1. creker
            04.12.2021 16:46
            -1

            Что именно делать на уровне компилятора? За меня ошибки он не обработает. Принимаю решения о важности той или иной ошибки я и никто другой. Список всех возможных ошибок - возможно тут компилятор сэкономил бы мне считанные минуты времени, но не более того. Этой проблемы, как я выше написал, просто нет на практике. Если я использую time.Parse и он мне вернул ошибок, потому что из фронта пришел запрос с кривой датой - что мне делать с этой ошибкой? Да ничего, вернуть обратно фронту. Если я делаю выборку из базы, то меня может интересовать разве что ошибка об отсутствующей строке. Все остальное меня не интересует, я ничего не могу полезного с этим сделать. Я пойду, найду в доке тип ошибки, который библиотека использует, и вытащу из нее ошибку какого-нить постгре и пойму, что в базе нет такой строки. Все.

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


            1. 0xd34df00d
              04.12.2021 21:00
              +4

              Что именно делать на уровне компилятора? За меня ошибки он не обработает.

              Зато он проверит, что вы обработали то, что нужно, и не потратили силы на обработку того, что не нужно.


              Если я использую time.Parse и он мне вернул ошибок, потому что из фронта пришел запрос с кривой датой — что мне делать с этой ошибкой? Да ничего, вернуть обратно фронту.

              И вы что, руками будете проверять возвращаемое значение time.Parse, а также всех остальных функций, которые парсят ваш вход?


              Поэтому меня всегда удивляли эти теоретическия рассуждения о мощи системы типов.

              Ну потому что на самом деле все эти наколенные рассуждения об обработке ошибок — это как попытка объяснить ООП на гавкающих котиках-собачках и прочих прямоугольниках. Тому, кто уже понимает, и так понятно, а кто не понимает, у того вопросы, а нахрена оно всё так сложно.


              Я вон недавно написал немного кода, где поддержка от компилятора с точки зрения того, где какая ошибка может быть, была очень полезна и очень помогла. Помогла, в том числе, понять эту задачу чуть лучше, отбросить несколько невалидных решений, и так далее. Но только вот описывать эту задачу и её контекст (а там был целый стек из монадических трансформеров, и было важно, что это StateT поверх EitherT, а не наоборот, но неважно) — это сильно за рамками комментария.


    1. ncr
      02.12.2021 14:00

      Либо повреждение памяти. И тут никаким местом плюсы не являются адекватным языком. Он не предоставляет никаких средств для работы с ними в отличии от сравниваемых Go и Rust.

      А что именно имеется в виду под «работой»?
      Восстановление после повреждения памяти? Как-то сомнительно, можно примеры?
      Или все же предотвращение повреждений памяти в некоторых случаях за счет ограничений количества способов отстрелить ногу?


      1. creker
        02.12.2021 15:18

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


  1. Alex_ME
    02.12.2021 00:06
    +11

    Мне нравится Rust, в первую очередь системой типов (не идеально, но это пока самое крутое, что я видел в менйстримных языках). Тем не менее, ИМХО, "ручное" управление памятью - это удел определенных узких задач, системного и встраиваемого ПО, а чаще всего GC достаточно. От того мне странно видеть, что если где и есть Rust - так это бэкенд, а в более низкоуровневые ниши, для которых Rust, ИМХО, подходит лучше всего, он проникает медленно.


    1. amishaa
      02.12.2021 00:34
      +1

      Не бесконечно-далёкая система типов есть в Скале.
      Там есть родовые травмы, связанные с Java (а именно проблема с null'ами, от которой полностью не получается уйти), но типизацию с трейтами она позволяет (и проверяет в compile-time)


      1. WASD1
        02.12.2021 13:33
        -1

        А вы можете объяснить чем Scala так хороша?


        Я вот её не понимаю, откуда взялся интерес к ней, на грани хайпа.
        Вроде бы там две родовые травмы, сводящие всю красоту на нет:
        1. Нетранзитивность признака "mutable". Ну какой смысл иметь иммутабельную коллекцию (или объект), содержащую в себе мутабельные данные?
        2. Довольно много неявных преобраований.

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


        1. AnthonyMikh
          02.12.2021 16:55
          +1

          Вот с первым, к стати, у Раста намного лучше — вплоть до того, что мне кажется примерно так и должна выглядеть (с поправкой на бОльшую высокоуровневость, позволяющую прятать сложность кода в машинерию компилятора) "человеческая версия Хаскеля" — жёстко разделяя данные, которые "могут измениться" и "не могут измениться".

          Это пока вы не прочитаете про interior mutability.


        1. 0xd34df00d
          02.12.2021 19:08
          +2

          "человеческая версия Хаскеля" — жёстко разделяя данные, которые "могут измениться" и "не могут измениться".

          А чем хаскелевское «может измениться» aka TVar/MVar/etc и «не может измениться» в виде любого другого типа не жёстко разделяется?


          1. WASD1
            06.12.2021 14:29

            В Хаскеле достаточно строгости в этом месте.
            Просто в других местах строгости излишне.

            А когда начинают делать условный "best FP + OOP hybrid" - то ослабляют строгость везде. Где надо и где не надо.


        1. GeorgeII
          02.12.2021 23:05
          +2

          Я вот её не понимаю, откуда взялся интерес к ней, на грани хайпа.

          Интерес к ней взялся в то время, когда в джаве был ужасный застой. Она дала хороший буст всей jvm экосистеме, как минимум джаве (лямбды, функциональные интерфесы, стримы, Optional, type inference - это что в голову сразу пришло. Естественно в джаве это все появилось не просто так, а благодаря вынужденной конкуренции) и котлину (тут проще будет перечислить отличия, чем сходства).

          1. Нетранзитивность признака "mutable". Ну какой смысл иметь иммутабельную коллекцию (или объект), содержащую в себе мутабельные данные?

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

          case class Item(id: UUID, name: String)
          case class Quantity(value: Int) extends AnyVal
            
          case class Customer(firstName: String, lastName: String)
          case class Purchase(items: Map[Item, Quantity], total: BigDecimal)
          
          val customer = Customer("Kyle", "Johnson")
          val purchase = Purchase(
            Map(Item(UUID.randomUUID(), "milk") -> Quantity(2)), 
            BigDecimal(5)
          )
          
          val completelyImmutableNestedStructure = List(
            customer -> purchase // <- this is a tuple
          )


          1. WASD1
            06.12.2021 14:53

            >> Интерес к ней взялся в то время, когда в джаве был ужасный застой. Она дала хороший буст всей jvm экосистеме, как минимум джаве (лямбды, функциональные интерфесы, стримы, Optional, type inference - это что в голову сразу пришло.
            Спасибо.
            А вот подскажите ещё такой момент (по вашему мнению, разумеется): то, что популярность Scala хм "стагнирует" на сегодняшний день - у этого только маркетингово-рыночная основа (никто большой за неё не вписался) или всё-таки "по-честному" и технологически у неё нет особых крутых перспектив \ выгодных ниш и т.д. ?

            ПС.
            >> А тут не понял. А зачем вы нарочно мутабельные данные засовываете в иммутабельные коллекции.
            "моей" ситуации нет - я прошёл пол-курса Одерского на курсэре (потому, что захотел посмотреть "современных FP-OOP гибрид" и именно курс одерского мне советовали прям как "вау это нечто").
            По поводу "иммутабельной коллекции мутабельных данных":
            1. Мне не нравится, что сам язык такое позволяет.
            2. Если я правильно понимаю (из общих соображений) компилятор редко может доказать, что в коллекции "транзитивно иммутабельные" данные. И применить хорошие оптимизации.
            3. Ну и даже человек всегда должен помнить, что подлянка такого рода возможна (например предок иммутабельный, а в коллекции лежит его мутабельный потомок).

            Почему мне это не нравится (п1)
            ИМХО проникновение FP в OOP в том числе имело цель запретить плохие паттерны кода, те, в которых программисты чаще делают ошибки (ну и вообще снять ментальную нагрузку, особенно ментальную нагрузку в части неявных случаев), зачастую даже ценой скорости исполнения. Ну и этот случай явно противоречит данной цели.
            (а да сам Одерски многократно напирает на замечателность иммутабельных данных. Но сам же, хорошей поддержки в языке "транзитивной иммутабельности" не сделал).


            1. GeorgeII
              06.12.2021 18:00

              А вот подскажите ещё такой момент (по вашему мнению, разумеется): то, что популярность Scala хм "стагнирует" на сегодняшний день - у этого только маркетингово-рыночная основа (никто большой за неё не вписался) или всё-таки "по-честному" и технологически у неё нет особых крутых перспектив \ выгодных ниш и т.д. ?

              Совокупность этого всего. Скажем так, лучшие и самые удобные штуки из нее переняли (ну если не напрямую, то косвенно) в Java, C# и Kotlin. И в итоге при сравнении плюсов/минусов скалы и вот этой троицы при старте проектов, выбор все чаще выпадал в пользу троицы. Два основных минуса скалы на мой взгляд: байт-код несовместимость между версиями (что должны были пофиксить в недавней 3й версии, но уже поздно для возвращения в мейнстрим. Лет 5 назад сделали бы, то еще был бы шанс) и излишняя сложность (приходится учить и ООП, и околохаскелевскую функциональщину). В итоге "рыночек порешал". (а) Сложно стартовать проект, т.к. нет разрабов и каких-то вещей в экосистеме => (b) разрабы не изучают и не пишут инструменты, т.к. становится меньше проектов на рынке => возвращаемся к (a). И так по кругу. Довольно классическая картина затухания.

              По поводу того, что никто за нее не вписался - не совсем так. Вписывались, и многие. Твиттер так вообще чуть ли не все переписал на нее лет 10 назад, в волмарте писали на ней куски большие, в линкедине целую кафку сделали :) А уж количество компаний, которые писали на ней небольшие кусочки своего бэка, на порядки больше.

              и технологически у неё нет особых крутых перспектив \ выгодных ниш и т.д. ?

              На текущий 2021 год лично я вижу у нее 3 ниши. В порядке убывания:

              1) Спарк и потоковая обработка. Хотя в batch обработке уже можно уверенно сказать, что по сути победил питон со своим pyspark'ом, но вот в потоковой еще пока нет. И, например, на Flink с использованием Скалы сейчас пишут некоторые вещи в Netflix, в Alibaba. В Tesla вроде есть какие-то сервисы по стриминговой обработке на Скале (но тут могу напутать). В Spotify биндинги для Apache Beam сделали. Как видите, это не классический бэкенд, а компании и их конкретные сервисы с терабайтами и петабайтами данных с требованием обработки в реальном времени, что довольно специфичный случай. Ну и да, не удивлюсь, если года через 4-5 и тут окончательно вытеснится джавой с питоном по вышеуказанным причинам.

              2) Full FP style со всеми этими вашими эффектами, тайпклассами и монадами. В основном для более точного, корректного и тестируемого описание данных при моделировании доменной области. Самый крупный представитель, который приходит мне на ум - Disney+. У них крупные части сервисов billing system'ы написаны в таком стиле. Также видел пару статей, как некоторые компаниии обычные микросервисы пишут на этом стеке. По сути, это юзер-френдли хаскель.

              3) Акторная модель на Akka.

              То есть ниши есть, но в процентном соотношении относительно классического бэкенда - капли в море.

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

              А можете пример какой-нибудь привести таких оптимизаций? Там и так иммутабельные коллекции написаны здорово. Построены на всяких trie и HAMT структурах с применением structural sharing. Мне в голову так сейчас не приходит пример, где информация об иммутабельности содержащихся внутри данных может помочь компилятору в оптимизации еще больше. То, что это способно выстрелить потом, когда бесконтрольно из разных частей кода менять состояние - тут да, не спорю, об этом следующий пункт.

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

              Это действительно возможно. Но перед этим нужно явно совершить действие с отходом от стандартных практик. Вот тот человек, который явно пропишет var вместо рекмендованного val или вложит внутрь изменяемую коллекцию, предварительно импортировав ее - именно он во время этих действий и должен осознавать данные вещи. А я не знаю, кстати, в хаскеле или расте компилятор умеет такую вложенность проверять?


              1. 0xd34df00d
                06.12.2021 19:26
                +2

                По сути, это юзер-френдли хаскель.

                Как хаскелисту интересно — что делает скалу более юзер-френдли?


                А я не знаю, кстати, в хаскеле или расте компилятор умеет такую вложенность проверять?

                В хаскеле — и да, и нет.


                Нет, потому что у вас вполне может быть иммутабельный список мутабельных MVarов (ячеек, чьё содержимое можно менять), и для компилятора он не будет ничем отличаться от списка иммутабельных интов — все функции, работающие для обычного списка, будут работать и на нём.


                Да, потому что вы ничего с этой мутабельностью вне мутабельного контекста сделать не сможете. Взять head или tail сможете, посчитать длину списка сможете, а вот изменить значение в одном из MVarов — нет. Для этого нужно будет заворачиваться в монаду, и это сразу будет видно.


                1. GeorgeII
                  07.12.2021 09:48

                  Как хаскелисту интересно — что делает скалу более юзер-френдли?

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


              1. amishaa
                06.12.2021 19:30

                Спарк и потоковая обработка. Хотя в batch обработке уже можно уверенно сказать, что по сути победил питон со своим pyspark'ом, но вот в потоковой еще пока нет

                У меня есть задача обработки данных батчем, и от скалы (или какого-нибудь другого jvm) особо не уйдёшь в user-defined функциях - у меня получилось отличие примерно на треть общего времени (не времени одного шага, а вот прямо всего - с вычитыванием данных вначале и последующей довольно тяжелой агрегацией и т.п.) в пользу нативной скалы.


                1. GeorgeII
                  07.12.2021 09:36

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


              1. amishaa
                06.12.2021 19:45

                Это действительно возможно. Но перед этим нужно явно совершить действие с отходом от стандартных практик. Вот тот человек, который явно пропишет var вместо рекмендованного val или вложит внутрь изменяемую коллекцию, предварительно импортировав ее - именно он во время этих действий и должен осознавать данные вещи. А я не знаю, кстати, в хаскеле или расте компилятор умеет такую вложенность проверять?

                Ну вот буквально на днях встречал вот такую проблему:
                Заведём мутабельный case class: case class Counter (var count: Int) {def inc: Unit = {count = count +1; }}
                После этого неправильно воспользуемся List.fill:
                val t = Counter(0)
                val l = List.fill(5)(t)
                Ну, а после этого попробуем инкрементировать каждый независимо:
                l.foreach(_.inc)
                И мы, внезапно, получили в каждом из каунтеров 5 (и это, на самом деле, даже один каунтер).

                Раст такое отлавливает - либо нужно явно дуплицировать объекты перед складыванием в массив, либо борроу чекер скажет, что вы пытаетесь взять на редактирование ссылку, которая не уникальна.


                1. GeorgeII
                  07.12.2021 09:43
                  -1

                  Заведём мутабельный case class

                  Вот здесь и кроется опасность :)

                  Как альтернатива, один из двух вариантов:

                  case class Counter(count: Int) {
                    def inc: Counter = this.copy(count = this.count + 1)
                  }
                    
                  val a = Counter(1)
                  val b = a.copy(a.count + 1)
                  val c = a.inc

                  А если совсем сложные структуры, то там Monocle как запасной вариант есть.

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


              1. WASD1
                06.12.2021 21:00

                Спасибо за столь подробный ответ.

                >> а можете привести пример таких оптимизаций
                Из простого, что сразу пришло в голову
                - хэшировать какие-то значения (банально Hash \ toString);
                - поднимать обращения к внутренностям как можно выше в коде;
                - если вы доказали, что в ф-ии нет побочных эффектов, а только чистые вычисления переходить к следующему пункту;
                Из продвинутого (для этого нужны "чистые функции" - т.е. всё-таки ещё доп-условия)
                - конкуррентный доступ эквивалентен обычному (т.е. никаких доп-инвариантов);
                - просто запускать любые вычисления над такой коллекцией в параллельном потоке по готовности входных данных.

                >> А я не знаю, кстати, в хаскеле или расте компилятор умеет такую вложенность проверять?
                Я бы ответил "да" и "да" (но в Rust меня поправили, что есть interior mutability).


    1. AnthonyMikh
      02.12.2021 16:54
      +4

      Тем не менее, ИМХО, "ручное" управление памятью — это удел определенных узких задач, системного и встраиваемого ПО, а чаще всего GC достаточно.

      На практике на Rust писать почти так же удобно. RAII всё-таки рулит.


      1. WASD1
        06.12.2021 15:03

        Смотрите вот при решении "задачек" по ощущениям кода на Rust примерно вдвое больше, чем на Go ("задачек" - потому, что при желании освоить язык первый (и иногда последний хД) этап: решить N задачек на нём на leetcode).

        Т.е. RAII разумеется рулит, но это проблему решает лишь частично.


        1. Alexei_987
          06.12.2021 20:28
          +2

          leetcode тут не очень показательный пример. Обычно на нем задачки имеют довольно небольшой обьем кода и плюс вы решаете задачу до конца за условно 20 минут-1час и после этого забываете это решение на всегда. Rust для такой задачи может быть использован, но не покажет в полном обьеме своих преимуществ. Вот если у вас кодовая база в 50К строк которую нужно поддерживать много лет, вот в ней у вас будет профит в том плане что при добавлении новых фич или рефакторинге вы будете очень много ошибок/проблем видеть еще на этапе компиляции а не в момент запуска в прод. Например добавили новый тип ошибок в enum и теперь надо везде добавить их обработку


          1. powerman
            07.12.2021 18:50

            Например добавили новый тип ошибок в enum и теперь надо везде добавить их обработку

            В Go это контролируется линтером.


  1. artemisia_borealis
    02.12.2021 01:36

    Интересно было бы сравнить не тройку Rust/Go/C++, а четвёрку Rust/Go/D/C++.
    Почему в сейчас не стоит начинать новый большой проект на C++ довольно-таки понятно; накушались все. А вот выбор Rust/Go/D уже не так очевиден.


    1. newpavlov
      02.12.2021 02:03
      +10

      Если не углубляться в детали языка и историю развития, то основная проблема D в том что он "не взлетел", что лишает его синергетического эффекта, а учитывая возраст языка и наличие сильных конкурентов вероятность "взлететь" в будущем слишком мала дабы серьёзно в него инвестировать.

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


  1. QeqReh
    02.12.2021 06:39
    -2

    У Григория явно мало опыта в Go.

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


    1. grigoryvp
      02.12.2021 13:59

      Тут не вопрос "простоты", увы ???? Про ожидаемые и неожиданные ошибки, а также связанные с этим беды я еще семь лет назад рассказывал на PiterPy: https://youtu.be/hzVECcMI8ys


      1. QeqReh
        02.12.2021 14:37

        Спасибо за ссылку. После просмотра постараюсь ответить по существу.


    1. TargetSan
      02.12.2021 15:13

      Насколько я понимаю, проблемы boilerplate кода вида


      ..., err := do_something()
      if err != nil {
          return ..., err
      }

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


      1. grigoryvp
        02.12.2021 15:32

        Решает. Enums + pattern matching творят чудеса: https://fettblog.eu/rust-enums-wrapping-errors/


        1. TargetSan
          02.12.2021 15:43
          +2

          Вопрос был про Go:


          У Григория явно мало опыта в Go

          Про ситуацию в Rust я в курсе, спасибо.


          1. grigoryvp
            02.12.2021 15:49
            +1

            Много комментов) Главная проблема в Go как я ее вижу не в том, что синтаксис избыточен (так же как в Java это может скрыть IDE) или можно легко облажаться (на это есть линтеры). Фундаментальная проблема в том что, имея на руках err, у нас нет никакого простого способа понять диапазон возможных значений. Вот сделали мы сетевой запрос, и в данной точке кода ожидаемы для нас ошибки - все, что связано с сеткой. Это мы хотим обрабатывать. А вот если наш сервер ответил 500, или прислал невалидный JSON, или память закончилась - это мы обрабатывать категорически не хотим. А хотим, к примеру, позвать взрослых записав стектрейс падения, и перезапуститься с надеждой на лучшее, если мы микросервис. В Go (и всех других мейнстримовых языках кроме Rust) я не знаю другого способа кроме как поплакать над калечной документацией и пойти читать ВЕСЬ код используемой библиотеке. Чтобы потом через пару месяцев обнаружить в проде неожиданное значение, потому что либу порефакторили и теперь она при временной недоступности сетки операционкой возвращает какой-нибудь OSError (не сама, конечно, а на три уровня абстракции вглубь, но нам от этого не легче).


            1. TargetSan
              02.12.2021 16:03
              +1

              Так вопрос был не к вам, а к QeqReh, который говорит, что с ошибками в Go просто работать. Чего я пока что не наблюдаю.


              1. creker
                04.12.2021 16:57
                -3

                Я могу тоже подтвердить, что работать с ними просто и удобно. Наши мысли с @powermanполностью совпадают, процитирую - "на практике, нас интересуют либо отдельные специфичные классы ошибок, либо отдельные конкретные ошибки - в общем, те, которые мы хотим обработать и знаем, как именно их надо обработать". Практически слово в слово как я выше написал. Поэтому ответ на вопрос, как узнать все типы ошибок, очень простой - этот вопрос не всплывает на практике, когда мы не спорим о языках и не строим теории о мощи типов, а тупо пишем код, чтобы он работал и работал надежно.

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


            1. powerman
              03.12.2021 03:43

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

              На практике сделав сетевой запрос, который, да, мог вернуть сетевые ошибки, ошибки возвращённые сервером, и местные ошибки парсинга/валидации ответа - нас просто не интересуют "все возможные классы ошибок". На практике, нас интересуют либо отдельные специфичные классы ошибок (которые вполне реально определять через интерфейсы вроде Temporary() bool и Timeout() bool), либо отдельные конкретные ошибки - в общем, те, которые мы хотим обработать и знаем, как именно их надо обработать. В сухом остатке - "все остальные", с которыми мы всё-равно без понятия что делать специфического, поэтому они все обрабатываются одинаково (повтором запроса, возвратом выше, логированием и игнором, etc.).

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

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

              При этом надо учитывать, что ни Rust ни Go не решают эту проблему на 100%:

              1. при обновлении на новую версию библиотеки зачастую никто не проверяет "а не появились ли в ней новые возвращаемые ошибки, которые случайно могут попасть под существующие шаблоны/классы обрабатываемых ошибок но которые нужно в некоторых точках обработать иначе"…

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


              1. grigoryvp
                03.12.2021 10:09
                +1

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


                1. powerman
                  03.12.2021 12:28

                  Ну вот на мой взгляд ожидаемые нашей логикой ошибки определяются от предметной области и задач нашего приложения, а от используемого языка зависят слабо (только в тех местах, где язык/библиотеки сами добавили какой-то accidental complexity, которую надо учитывать конкретно на этом языке/с этой библиотекой). Или, говоря иначе, все ожидаемые ошибки и логику их обработки мы обычно в состоянии перечислить на этапе написания ТЗ, задолго до того как мы запустим IDE с кодом и та сможет нам подсказать список возможных ошибок. :)


                  1. grigoryvp
                    03.12.2021 12:48
                    +2

                    Перечислить в свободной форме естественным языком - да. Выбрать нужные значения/исключения/типы/что-там-язык-предлагает - нет. Вот я до написания программы сформулировал "для меня ожидаемые ошибки все что связано с сетью, неожиданные - все остальное". Как я, имея в Go "err", смогу определить, какие коды ошибок относятся к сетке, а какие "ко всему остальному"? Вангую, что сейчас вы напишите "ну как же, в документации написано!" Увы. Не написано. Авторы библиотеки сами могут не знать что их библиотека может вернуть, потому что библиотека использует другие библиотеки и "пробрасывает" наверх ошибки. Rust отвечает на этот вопрос с помощью Enum'ов. Go, Python, Ruby и все остальные - нет.


                    1. QeqReh
                      03.12.2021 13:27
                      +1

                      Как я, имея в Go "err", смогу определить, какие коды ошибок относятся к сетке, а какие "ко всему остальному"?

                      Типы ошибок на уровне http протокола: https://pkg.go.dev/net/http#pkg-variables

                      Определять можно через https://pkg.go.dev/errors#example-Is


                    1. powerman
                      03.12.2021 14:39

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

                      type NetworkError struct {
                        err error
                      }
                      
                      // где-то на нижнем уровне дёргаем сеть
                      result, err := someNetworkOperation(params)
                      if err != nil {
                        return NetworkError{err: err}
                      }
                      
                      // где-то на верхнем уровне отличаем сетевые ошибки
                      err := someComplexOperationInvolvingNetwork(params)
                      if errors.As(err, new(NetworkError)) {
                        // обрабатываем все сетевые ошибки
                      }

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

                      При желании можно придраться, что "все сетевые ошибки" не всегда эквивалентно "все ошибки возвращённые функцией выполнявшей сетевую операцию", но, опять же, это в теории - а на практике оно обычно вполне эквивалентно (в частности потому, что ошибка malloc превратится в панику, а не будет возвращена ошибкой).


                      1. grigoryvp
                        03.12.2021 14:56
                        +3

                        Авторы библиотек так делают? Когда я, как разработчик, пишу код на Go и вызываю рандомную функцию из рандомной библиотеки - я могу быть уверен, что автор аккуратно обернул NetworkError, FilesystemError, XmlDecodeError, ...? ???? Ну и, наконец, даже если такая волшебная библиотека есть - как я за разумное время, находясь в IDE, узнаю, что нужный мне тип называется "NetworkError"? Автор это ВСЕ в документацию вынесет? Как раньше в Java пробовали с checked/unchecked exceptions?

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


                      1. powerman
                        03.12.2021 16:08

                        Нет, это делают не авторы библиотек, а мы в своём коде. Так что проблемы с докой нет - свой код мы знаем.

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


                      1. grigoryvp
                        03.12.2021 17:59
                        +3

                        Двадцать лет назад я такое слышал про плюсы. "Да, эксепшны сломаны, но вы ведь можете в своем коде обернуть ВСЁ, в чем проблем?" Проблема в том, что обернуть все в своем коде - это чудовищные затраты сил на разработку. В то время как с помщью Rust мы просто пишем Err и выбираем из предложенного IDE нужные нам для обработки ошибки. Все делает компилятор + IDE, нам не нужно самим выстраивать "параллельное" дерево кодов ошибок.


      1. QeqReh
        03.12.2021 06:05
        +1

        а также случайного игнорирования тех самых err он не решает никак.

        Игнорировать ошибки и не надо. Её надо все равно как-то обработать. Хотя бы отправить в log.Debug().

        Ваш код я бы написал так:

        if err := some(); err != nil {
            return _, errors.Wrap(err, "couldn't do something")
        }

        errors.Wrap() сохраняет типы ошибок.
        И далее можно проверить с помощью errors.Is()

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


  1. pachanga
    02.12.2021 13:54
    +2

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


    1. domix32
      02.12.2021 20:24

      Есть вот такая история, но я так понял там я так понял был не руби изначально.


      Whoops! I Rewrote It in Rust

      Можно полистать по TWIR, там тоже были вроде success story по вашей теме.


  1. caballero
    03.12.2021 02:18
    -2

    Хайп вокруг раста вроде начинает проходить. Как уже прошел вокруг го. То есть серебряной пулей не оказался и теперь займет ту нишу для которой предназначен а не для написания всего на свете.


    1. QeqReh
      03.12.2021 08:00

      Для написания всего на свете есть C++ и Java)


  1. Medeyko
    03.12.2021 18:32
    +2

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

    Вы так несколько раз педалируете "толстенность". А про какую книгу речь? Если про "The Rust Programming Language" ("The Rust Book"), то, по-моему, не такая уж она и толстенная, хотя, конечно, и раз в семь больше "The Little Go Book", но зато раза в полтора меньше "The C++ Programming Language". Толстая - да, но толстенная ли? Я имею в виду, что это не книга масштаба "Войны и Мир" или "Искусства программирования" - такие масштабы могут внушать ужас уже сами по себе, а "The Rust Programming Language" по размеру соответствует типичной серьёзной книге, не более того, по-моему.


    1. DirectoriX
      03.12.2021 21:43
      +1

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


    1. grigoryvp
      03.12.2021 22:08
      +1

      The Rust Book. Я недавно брал интервью у автора. У меня книжка заняла несколько месяцев вдумчивого чтения по вечерам, это с большим C++ бэкграундом. По сравнению с хорошими учебниками по Python/JS она толстая и сложная.

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


      1. Medeyko
        04.12.2021 20:29

        Я говорил только про объём. Со сложностью сложнее, да. Кто-то проглотит за несколько дней, кому-то потребуется полгода. Как правильно сказал Клабник в интервью с Вами, очень сильно зависит от опыта человека, с какими языками программирования он уже имел дело - если с какой-то технологией он уже сталкивался, синтаксис её применения в Rust'е он может запомнить за считанные минуты.

        А какие конкретно хорошие свободные учебники по Python и JS Вы имеете в виду, с которыми имеет смысл в рамках нашего разговора сравнивать The Rust Book?


        1. grigoryvp
          04.12.2021 20:37
          +1

          Мне нравятся "Eloquent JavaScript" и "Python Crash Course".


  1. Cykooz
    08.12.2021 13:09

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

    Rust ничего такого не гарантирует - достаточно просто поделить число на 0 и приложение "упадёт" как и в других языках программирования. Практически все гарантии Rust связанны только с безопасной работой с памятью. А про "падения" надо всегда пояснять, что имеется ввиду корректное завершение приложения, при возникновении необработанных ошибок. Без вредных сайд-эффектов, вроде выполнения стороннего кода или утечки наружу каких-то приватных данных (но это неточно, т.к. через unsafe и указатели скорее всего можно поковыряться в стеке).

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