company_banner

«Мы даже не пытаемся запустить старый код, такой задачи у нас не стоит в принципе» — Роман Елизаров о разработке Kotlin

    Если хочешь в чем-то разобраться — учись сразу у лучших. Сегодня на мои вопросы отвечает бог корутин и concurrency, Рома elizarov Елизаров. Мы поговорили не только о Kotlin, как вы могли бы подумать, но ещё и о куче смежных тем:

    • Golang и горутины;
    • JavaScript и его применимость для серьезных проектов;
    • Java и Project Loom;
    • олимпиадное программирование на Kotlin;
    • как правильно обучаться программированию;
    • и другие волнующие вещи.



    Kotlin — молодец!


    Привет. Давай вначале пару слов о себе. Ты давно занимаешься Kotlin?


    У меня с Kotlin давняя история. В 2010 году Kotlin начинался как проект в JetBrains, где я в тот момент еще не работал. Но Макс Шафиров (он тогда занимался Kotlin и был одним из инициаторов этого движения внутри JetBrains) пригласил меня стать внешним экспертом и посмотреть на дизайн, прокомментировать. Изначально язык дизайнился для решения своих проблем, ведь у JetBrains своя большая база кода на Java, с понятными проблемами, которые в коде постоянно есть, и хотелось сделать язык для себя, чтобы свой код писать приятней, эффективней, с меньшим количеством ошибок. Просто провести у себя модернизацию. Естественно, это быстро переросло в идею, что раз у нас такие проблемы — значит, и у других такие проблемы есть, и им нужно было подтверждение от других людей, что они идут правильным путем.

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

    В самой работе команды я не участвовал, просто периодически поглядывал, участвовал в соревнованиях на Kotlin (Kotlin Cup). Я всю жизнь занимаюсь соревнованиями, но сам уже тогда активно не участвовал. Например, я бы не вышел в финал соревнований вроде Facebook Hacker Cup, форма не та из-за того, что в соревнованиях уже не участвую на постоянной основе. А в Kotlin Cup я принял участие и, так как он не собрал широкую аудиторию, я легко вышел в финал.

    На тот момент (2012-2013 гг.) Kotlin представлял собой грустное зрелище с точки зрения тулинга, потому что там всё тормозило. С тех пор команда проделала огромную работу. Я пришел в команду два года назад, сразу после релиза 1.0 и до того, как Google официально признал язык. В команде я занялся всякой асинхронностью и корутинами, просто потому что так вышло, что у меня подходящий опыт, я много в DevExperts занимался всякими разными большими энтерпрайзными системами, и там много асинхронности и коммуникации. Поэтому я хорошо представлял себе проблемные места — что надо чинить и что у людей болит. Это очень хорошо легло на нужды Kotlin, потому что болит не только у нас. Болит у всех. Даже в JVM занялись Project Loom, что как бы намекает, что болит у всех. Я до сих пор занимаюсь котлиновскими библиотеками, и основной наш фокус — на всякие connected-приложения и асинхронность.


    То есть ты занимаешься в основном библиотеками, не компилятором и вот этим всем?


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


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


    Да, можно найти кучу всяких задач, потому что я постоянно на что-то наталкиваюсь.


    Ты упомянул Project Loom. Его сделал парень, который сделал Quasar. Cо стороны это выглядит очень забавно, я как раз хотел на Хабру писать статью про Loom. Можешь рассказать что-нибудь про него?


    Видел презентацию, идея понятная. Корутины и асинхронное программирование нужны всем. Например, на прошлом JPoint ребята из Alibaba рассказывали, как они хакнули JVM и прикрутили себе файберы хотспот, просто накатив туда патчик, который даже не они написали, а какие-то ребята до них. Они уже потом подпилили под себя. Замечательный доклад. Очень рекомендую.


    А ты рекомендуешь так делать?


    Так делать в энтерпрайзах приходится. Каждый большой энтерпрайз, выше какого-то размера, когда у тебя начинает работать несколько тысяч человек (а для кого-то и меньше), мейнтейнит свой хак OpenJDK. И конечно, если у тебя есть бизнес-критичные юзкейсы, то почему бы и не хакнуть что-то под себя, не вижу в этом никакой большой проблемы. Не то чтобы я это рекомендую, но приходится. Если в HotSpot нет легковесных потоков, то что делать? Это, собственно, говорит о том, что людям надо, что назрело. И фидбэк, который мы получаем по корутинам, тоже говорит о том, что да, назрело, людям нужны легковесные потоки, у людей вагон юзкейсов для легковесных потоков. Тот факт, что они должны как-то поддерживаться в JDK, давно назрел, и в этом смысле я не сомневаюсь, что когда Loom рано или поздно дойдет по продакшна, это будет востребовано. Есть люди, которым это надо. Есть люди, которые даже ради этого патчат HotSpot.


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


    Это довольно типичная проблема. И веб-сервер, и application-сервер, и бэкенд. Если ты посмотришь ту же презентацию Алибабы, почему и понадобилось это дело, то у них не веб-сервер, у них классическая энтерпрайзная архитектура, у них на бэкенде на Java написаны всякие сервисы, эти сервисы находятся под нагрузкой. Я с таким же работал в DevExperts: сервисы под нагрузкой, тебе приходят запросы, которые ты не сам ведь обрабатываешь — в современном мире у тебя всё connected. И вот этот запрос ты не сам обрабатываешь, а еще 100500 всяких других сервисов вызываешь и ждешь, пока они ответят. И если эти сервисы тормозят, то у тебя много потоков ждет. Ты не можешь себе позволить иметь десятки тысяч этих ждущих потоков. И у тебя получается просто из-за какой-то ерунды следующее: один сервис, который ты используешь, тормозит, и куча потоков стоит и ждет. И сейчас это очень большая проблема.

    Одна из причин, почему люди массово мигрируют на Go — не потому, что язык хороший, а потому что там легковесные потоки из коробки, и такой проблемы уже нет: горутины могут ждать, и они ничего не стоят. В том же Alibaba, решение, которое они заимплементили — оно вообще тупое из всех тупых. Они не очень легковесные в том смысле, что они каждой корутине выделяют один большой стек по 2 мегабайта, хакнув HotSpot, чтобы можно было эти стеки переключать. Они экономят физический поток, но не экономят стеки. И для них решение работает — оно, кстати, очень простое, у них патч HotSpot, насколько я понимаю, не очень большой. Ребята из Loom затеяли нечто более глобальное. Они решили сэкономить не только на физических потоках, но и на стеке, чтобы не тратить 2 мегабайта на поток. В прототипе текущий стек через HotSpot проходит, его копируют в маленькую хиповую структуру. И могут дальше этот физический стек переиспользовать для других целей.


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


    Да, там вагоны хаков и оптимизаций. Что в итоге из этого получится — очень сложно сказать. Потому что на примере подхода с копированием, сразу возникает следующая проблема: а что делать с нативными вызовами? Изнутри нативного вызова ты уже не можешь скопировать стек нативного вызова. В подходе Alibaba такой проблемы нет. Нативный, не нативный — какая разница, ты просто тот стек отцепил совсем и оставил его в покое, подцепил другой стек, всё работает. И тут рано говорить, что получится или не получится, с этим нативным стеком иногда можно жить, иногда нельзя — на этом этапе рано сказать. Например, как это в Go реализовано — там совсем другой механизм. Пока ты выполняешь гошный код, используются маленькие гошные стеки. Соответственно, когда гошный рантайм вызывает функцию, он смотрит, сколько нужно стека. Если текущего стека не хватает, он перевыделяет — увеличивает размер выделенного стека. Если, соответственно, ты делаешь нативный вызов, то они уже берут какой-то большой нативный стек из некоего пула и используют его.


    И для гошного кода тоже?


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

    Нам постоянно задают вопрос: «Что быстрее? Что подходит? Как вы в корутинах это делаете?» Мы в корутинах не хакаем JVM. Наша задача заключается в том, чтобы это работало под обычным JVM. И чтобы на Android тоже работало. Там свой ART, который тоже о корутинах ничего не знает. И поэтому, естественно, нам приходится ручками генерировать байткод, который делает что-то очень похожее на копирование стека, который делает Loom, только мы это делаем в байткоде. Берем его, когда он уже засаспендится. Берем стек, разматываем и копируем в хип. Мы не на рантайме, который бы это за нас делал, у нас сгенерирован байткод, который это делает. Он сохраняет и восстанавливает состояние корутины. Из-за того, что мы не делаем рантайм, естественно, у нас от этого больше оверхеда. В рантайме ты можешь все сделать быстрее. С другой стороны, если ты корутины используешь для асинхронного программирования, то тебе надо заснуть, если ты ушел ожидать ответа от какого-то сервиса, а послать запрос в какой-то сервис так дорого, что весь оверхед на копировании стека вообще никого не волнует — медленный он у тебя или быстрый — вообще становится неважно. Да, если ты это используешь именно для асинхронного программирования. У нас на корутинах в Котлине это замечательно скейлится, как и показано в прототипе Project Loom.

    Другое отличие — так как мы в Котлине вынуждены делать это в байткоде, то у нас есть такой интересный побочный эффект. С одной стороны, вроде бы и неудачно, а с другой — наоборот. Заключается он в следующем: нельзя усыпить произвольную функцию. Нужно функции, которые могут заснуть, помечать модификатором suspend — явно пометить, что функция может приостановиться и чего-то ждать, что она долгая. С одной стороны, в Loom тебе это не нужно, потому что рантайм может усыпить что угодно. В решении от Alibaba то же самое — ты можешь у любого потока отобрать стек. Или в Go — там всё можно засаспендить, любой код может уснуть. Наплоди еще горутин и делай. С одной стороны, этот подход очень похож на программирование с тредами. Ты как бы программируешь как раньше, только теперь треды называются файберами и стали очень дешевыми. Если внимательно посмотреть презентацию того же Loom, выясняется, что файберы и треды — это всё-таки разные вещи. Как сделать так, чтобы старый код, который написан с тредами, прям совсем из коробки завелся на файберах — не очевидно, и что у них получится — никто не знает. Там начинаются проблемы: а что делать с дэдлоками, что делать с кодом, который соптимизирован на thread locals, опять же какие-то хэши свои локальные имеет или по thread ID хитро какие-то перформанс-оптимизации делает. И в Go та же самая проблема — когда хардварные thread ID не экспозятся, писать какой-то high performance-алгоритм становится нетривиально.


    А в Котлине такого нет?


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

    У нас в нашей модели нет никакого старого кода, только новый, который изначально готов к тому, что сегодня он на одном треде, завтра на другом, и если ему, например, нужно узнать, какой сейчас тред, он это узнает. Да, нужен thread local, но он может их узнать. Однако он должен быть готов к тому, что сегодня thread locals одни, а завтра — другие. Если он хочет, чтобы эти локалы путешествовали с ним, для этого есть другой механизм, корутинный контекст, где он может хранить свои вещи, которые будут вместе с корутиной путешествовать с треда на тред. Это, в каком-то смысле, нам упрощает жизнь, потому что мы не пытаемся старый код поддерживать.


    А с другой стороны, мы заставляем человека явно подумать над своим API, сказать: вот я пишу функцию на Kotlin с корутинами. Если раньше я смотрю на какой-то метод в своем коде, getЧтоНибудь, непонятно, этот метод быстро работает и возвращается сразу или пойдет в сеть и может час работать — я могу только документацию почитать и понять, как быстро он будет работать. А может, сейчас он быстро работает, а завтра придет программист Вася Пупкин и сделает так, что он теперь ходит в сеть. С Kotlin-корутинами мы даем гарантированный языком механизм с модификатором suspend. Я когда сам работаю с корутинами, смотрю на какую-то функцию, если не вижу модификатора suspend, значит, она быстро работает, всё локально делает. Есть модификатор suspend, значит, эта функция какая-то асинхронная, она пойдет надолго в сеть. И это помогает делать самодокументирующийся API, чтобы мы сразу видели, что нас ожидает. Это помогает сразу избежать тупых ошибок, когда я где-то забылся и где-то в коде вызвал что-то долгое, не подозревая об этом.

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


    Но есть же часть вещей, которые сложно запретить, например, какой-нибудь сетевой IO, файловый.


    Нет, сетевой IO как раз запретить достаточно легко. Вот файловый IO — сложно. Но здесь опять тонкий момент: для большинства приложений файловый IO — это быстрая вещь, и поэтому совершенно нормально, что он работает синхронно. Очень редкое приложение так много работает с IO, что для него становится проблемой тот факт, что это занимает так много времени. И здесь мы даем человеку возможность выбрать: ты можешь у нас напрямую делать файл IO и не париться, потому что оно будет блокировать, что происходит (потому что обычно это быстро). Но если конкретно в твоем кейсе просто какое-то очень долгое вычисление, вроде не асинхронное, но просто жрет кучу времени CPU, и ты не хочешь этим самым блокировать какие-то свои другие thread-пулы, мы предоставляем простой понятный механизм: ты заводишь отдельный thread pool для своих тяжелых вычислений, и вместо того, чтобы писать обычную функцию, которая fun computeSomething(), и писать в документации «Чуваки, аккуратно, эта функция может работать очень долго, поэтому внимание — не используйте ее где попало, не используйте в UI», мы предлагаем более простой механизм. Ты просто пишешь эту функцию как suspend fun computeSomething(), а для её реализации используешь специальную библиотечную функцию withContext, которая перекидывает вычисление на указанный тобой специальный thread pool. Это очень удобно: пользователю не надо больше парить мозг: он сразу видит suspend, знает, что этот вызов его тред не блокирует, и он может совершенно спокойно вызывать её из UI-потока и так далее.


    Она уже внутри переключится на нужный поток, а его поток не заблокируют. Это правильный separation of concern: пользователя не парит, как оно реализовано, а тот, кто реализует, может правильно перекинуть на тот пул, на который нужно, и правильно распределить вычислительные ресурсы в своем приложении. На практике это оказывается очень удобно с точки зрения стиля программирования. Надо писать меньше документации, компилятор больше проверит и поправит.


    Я думаю, насколько это безопасно. Может ли кто-то сломать thread pool или вломиться в чужие данные?


    Естественно, всё возможно. От кривых рук тяжело защитить. Понятно, что сколько бы мы ни писали в компиляторе всякие системы типов и проверки, всегда всё можно сломать. Вопрос в том, что компилятор должен помогать писать правильный код. К сожалению, мечта запретить писать плохой код утопична. Мы специально не включаем какие-то фичи в язык. В Котлине нет каких-то вещей из Java, если про них известно, что они в основном используются не по назначению и код с ними в основном плохой. Но и любую хорошую фичу, которая есть в Котлине, можно использовать не по назначению массой разных способов. Вариантов нет, к сожалению. Язык можно абьюзить по-разному. От кривых рук не защитишься никак.


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


    От каких вопросов?


    Например, один вопрос пережил двух человек: почему в Котлине нет raw types? Без дженериков.


    Потому что всегда можно написать звёздочку.


    А оно при этом будет совместимо с Java?


    Да.


    То есть у тебя есть какой-то джавовый метод, который требует что-то без дженерика. List, например.


    Если есть такой джавовый метод без дженерика, ты туда можешь передать любой List. Можешь List со звёздочкой передать, можешь со строкой. Котлин позволит тебе в джавовый метод передать любую дичь. Если он возвращает тебе raw List, то в Котлине ты получишь некий platform type, который ты можешь закастить, например, к List со звездочкой. Потому что raw type, по сути, в Java сделан не от хорошей жизни, а чтобы было проще мигрировать. Была Java, в которой не было генериков, теперь есть генерики, и чтобы люди не мучались, чтобы им не нужно было по всему коду эти угловые скобочки проставлять, была сделана эта специальная фича, raw type. Когда делали Котлин, такой проблемы не было — не было никакого старого кода, в котором генерики не указаны. Есть только новый код, в котором все типы — генерики. Поэтому проблема миграции со старого кода без генериков на новый код с генериками — её не существует, и нет смысла делать в языке такую фичу, как raw types. Она Котлину не нужна, так как тут нет migration story.


    Как работает кастинг platform type к котлиновскому?


    Платформенные в Котлине считаются flexible. Это проще рассмотреть на примере nullable-типов. Вот у тебя есть джавовский код, который возвращает String. В Котлине непонятно, там String или nullable String — это два разных типа. И мы ведь не знаем, какой String возвращается из джавовского метода — nullable или не-nullable. Поэтому Kotlin считает, что он может быть и такой, и сякой. И разрешает тебе его присвоить как в String, так и в nullable String. То же самое и здесь, когда ты получаешь какую-то слаботипизированную дичь от Java, ты на стороне Котлина всегда можешь указать просто более специфичный и правильный тип.


    А если ты указал неправильно?


    Если ты указал совсем неподходящий, то у тебя компилятор, естественно, ругнется, потому что он должен подходить. Flexible не значит что угодно, это не dynamic. Flexible указывает на диапазон — то, что сейчас вернула Java, ты потом можешь присваивать и в String, и в nullable String, но не в int.


    Там всё-таки есть какое-то прямое перечисление возможного, и оно там матчится.


    То же самое с сырыми типами, похожий механизм, но немного посложнее. То же самое с коллекциями. В Котлине коллекции разделяются на read only и mutable. У тебя бывает List и MutableList. Если тебе Java возвращает List, то фиг поймешь, что Java-программист имел в виду, можно мутировать его или нельзя, поэтому ты уже на Котлин-стороне можешь в List присвоить или в MutableList. Котлин более строго типизирован, чем Java. Соответственно, когда ты из Java что-то получаешь, ты должен более специфичный тип указывать и наоборот. В обратную сторону работает из коробки, так как джавовский метод принимает менее типизированные вещи, туда можешь любую подходящую котлиновскую штуку передать. Опять же, принимает, например, джавовский метод List, мы же не знаем, какой, поэтому можно туда и MutableList, и обычный List передать. А если ты будешь передавать не List, а String, то не разрешит.


    А когда используются джавовские либы какие-то либо, какой правильный подход? Надо заворачивать в какие-то врапперы котлиновские?


    Не, ничего не надо заворачивать. Котлин задизайнен так, чтобы джавовские библиотеки было просто использовать. It just works. Большинство Java библиотек просто работают с Котлином вообще без проблем. А если там еще nullability-аннотации прописаны, то Котлин сразу видит, nullable или нет результат. Но удобство зависит от дизайна библиотеки, конечно. У Котлина, например, специальный синтаксис, когда последнюю лямбду удобно передавать за круглыми скобочками. Например, в джавовых либах, у которых такой же порядок аргументов, понятно, что последним аргументом принимается какой-нибудь предикат. В Котлине ты можешь использовать его без всяких специальных адаптеров, просто можешь использовать красиво по-котлиновски. Берешь какую-нибудь JavaFx, и без каких-либо дополнительных адаптеров код на Котлине получается красивее, чем если бы ты JavaFx использовал на Java, удобнее, приятнее его смотреть. Понятно, что ты можешь еще написать себе каких-то адаптеров, и это будет еще круче. Но даже без них джавовские либы приятно использовать. Любая либа становится круче и удобнее, если ты просто начинаешь использовать её из Котлина, просто по факту использования Котлина.


    У тебя аж голос изменился, так эмоционально всё описываешь.


    Конечно. Я просто видел это много раз, тебе просто так приятнее программировать. Мы на такое же надеемся и с Kotlin Native. Идея та же: Котлин приятнее как язык, и мы там пытаемся сделать максимально seamless интероп со всякий C-шной экосистемой, просто чтобы дать людям возможность использовать их существующие либы из более приятного языка, без каких-либо барьеров.


    Кстати, а ведь при переходе на Native там не должен ли измениться смысл языковых конструкций?


    Конечно, у нас, даже если посмотришь на Kotlin JavaScript, какие-то конструкции немного по-другому работают. Мы не ставим целью добиться «Write once, run anywhere», не стоит такой цели, чтобы было абсолютно идентично. Наша задача немного другая: мы хотим некое подмножество языка, вроде Common Kotlin или Portable Kotlin, на котором ты будешь писать, и оно будет работать отовсюду. Понятно, что ты можешь залезть в какие-то платформо-специфичные штуки, и их поведение будет соответствующим, но это нормально. Если ты пишешь под одну платформу, и тебе всё равно, а под несколько платформ ты просто какие-то вещи будешь обходить стороной. И мы многие вещи специально не фиксим, чтоб сохранить перформанс.

    На JVM у нас перформанс, как у Java, на JS у нас перформанс почти такой же, как у JS, на Native нативный перформанс. Задача, в первую очередь, не смотря на эту переносимость, дать нативной платформе высокопроизводительный код, а не писать еще одну виртуальную машину. Многие пытаются транспилировать какую-нибудь джаву в тот же JS, попытка полностью эмулировать джаву. Эта попытка приводит к огромному перформанс-оверхеду. Какие-нибудь банальные глупые вещи: ты берешь double, конвертируешь в String. И на JVM-ном Котлине число 0 превратится в строку «0.0», а на нативном JS будет просто ноль. Разное поведение. Можно было бы на JS это пофиксить, но тогда все преобразования чисел в строки стали бы намного тормознее, потому что он будет обвешан дополнительными проверками — кому это надо? Пускай будет эта небольшая разница, зато нативный перформанс. У нас нет никакой своей специальной тормозной функции, которая преобразовывает числа в строки, у нас просто нативное JS-ное преобразование. Таких примеров очень много, где мы специально принимаем решение сделать разное поведение из перформанс-соображений. Но там, где это не критично. Всё-таки семантика основных конструкций языка — классы, наследование, вызовы и так далее — все работает так же. У нас есть пример огромных проектов, как собственных, так и внешних, в которых написано много кода на Котлине, он компилируется и работает под JVM, JS, Native — и всё это нормально работает, даже несмотря на то, что где-то поведение немного отличается. Все тесты проходит.


    А корутины работают на всех платформах?


    Да. Это фича языка, которой всё равно, под какую платформу ты её запускаешь.


    То есть вся размотка стека, вот это всё…


    Да, это всё исключительно компиляторная фича. Нам ведь не нужна поддержка от платформы, вот в чем фишка. В отличие от проекта Loom и так далее. Мы не делаем это каким-то хаком в JVM. Это фича компилятора, поэтому мы можем то же самое сделать под любую платформу.


    А если взять котлиновский код и попытаться его зареверсить, например, в Java?


    То ты увидишь там всю эту дичь, которую компилятор выдал. Зато у тебя красивый код на входе. В смысле — зато ты написал красивый код. Это уже задача компилятора сделать из этого нечто, что будет работать. В Котлине много конструкций высокого уровня, которые потом превращаются в какую-то низкоуровневую пургу. Например, пишешь for ( i in 0..10 ), и это разворачивается в цикл for (int i = first и прочую дичь. Но писать приятно, так что какая разница, как оно там компилируется. Оно работает. Быстро, потому что разворачиваются в соответствующие нативные конструкции.


    А кто-нибудь сравнивал одни и те же программы на разных платформах?


    Нет, и более того… несмотря на цель сохранить перформанс, платформы изначально несравнимы. У них разные задачи. Какой смысл сравнивать JVM и JS.


    А какой смысл писать на сервере на Node.js?


    Так понятно зачем! Не ради перформанса. Люди пишут для того, чтобы реюзать своих JS-программистов. Зачем изучать новый язык, новых программистов нанимать, если JS-программисты спокойно пишут бэкенд. Мы такую историю хотим дать с более хорошим, типизированным языком. Если ты умеешь программировать на Котлине, то один раз написал бизнес-логику и дальше пожалуйста — гонять ее на джавовом бэкенде, годняй на JS-фронтенде, гоняй в нативном микросервисе или чем-то — пофиг. Если код на Котлине, он сможет скомпилироваться куда угодно. В этом как раз цель Котлина.


    У тебя были доклады вроде «миллиона котировок». Ты в своих предыдущих проектах стал бы использовать Котлин, если бы он был изобретен сильно раньше?


    Конечно. Всё, что я говорил в те времена, можно делать на Котлине. На JVM это просто более удобный язык, чем Java, компилирующийся в тот же самый байткод. Поэтому не использовать его на JVM большого смысла нет. Разве что у тебя legacy, enterprise, и это просто запрещено. Или если ты делаешь библиотеку, которую должны использовать клиенты, которые Котлин не могут использовать. А если ты пишешь для себя, нет никакого смысла не писать на котлине под JVM. Байткод тот же самый, а на входе — не только более удобный, но и более компактный язык. Он еще и более типизированный, защищает от большего количества ошибок. Но не заставляет писать совсем жестко типизированную дичь, — как всегда в JVM можно сказать компилятору «я знаю лучше тебя». Механизмы обойти компилятор у тебя есть. Но в обычной практике API на Котлине получаются более документированными, более строгими и более безопасными. Код получается надежней, реже падает по исключениям. Его читать проще, меньше воды в коде. Многие штуки, когда в Джаве пришлось бы писать бойлерплейты, в Котлине пишется в одну или несколько строчек. С таким кодом приятней работать, в нем меньше воды и больше сути, которую ты хотел выразить.


    В эти выходные Роман будет на фестивале TechTrain с докладом «Зачем нужен еще один язык программирования?». Об этом фестивале мы совсем недавно писали на Хабре. Загляните, вдруг понравится.

    Обучение и олимпиады


    Раз уж ты начал говорить про JS и обучение, насколько сложно на Kotlin переучиться с Java?


    Вот с Java как раз очень легко и вообще никаких проблем. У нас есть и книжка «Kotlin in Action», и сайт, ориентированный на Java-программистов. По опыту, Java-программисту нужно потратить от двух дней до двух недель, и всё, вышел из тебя отличный Kotlin-программист. И это не случайно получилось, это «by design». Изначально в дизайне Котлина заложено, что Java-программистам должно быть легко на него перейти. Велосипед не изобретали. У Андрея Бреслава на прошлом JPoint есть хороший доклад, откуда что Kotlin позаимствовал. Есть слайд о том, откуда родились разные языковые конструкции. Видно, что 60-70% взялись из Java. Оно и называется как в Java, чтобы было проще, чтобы не нужно было изучать что-то сильно новое. Это большие языки типа Java могут себе так позволить.

    У них вообще есть такая дизайн-цель — об этом еще Гослинг говорил, что если мы что берем, то обязательно называем по-другому. Никогда ничего не берется as-is. Мы люди большие, можем себе позволить, пускай люди учат. В Kotlin же, если ты уже знаешь, что такое «класс», это и должно называться «классом». Если знаешь, что такое «интерфейс» — должно называться интерфейсом. Люди знают цикл while — нет смысла его переименовывать. Хотя можно найти 100500 более хороших названий для него. Но зачем? Кроме того, большинство наших обучающих материалов рассчитано на Java-программистов. Даже я делал какие-то доклады, «Введение в Kotlin», и все это расчитано на них, нужно рассказать только какие основные вещи в Котлине новые и интересные.


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


    Для таких новичков, что совсем-совсем, или для перебежчиков с других языков?


    Да, для совсем новичков. Еще стараемся сотрудничать с вузами. На пути переучивания Java-программистов мы очень далеко продвинулись. Android-программисты тоже — больше половины, вроде бы по последним данным. И все переучились без проблем. Миллион программистов как минимум уже переучились, и никаких проблем нет. А вот на обучении с нуля — мы находимся на очень раннем этапе пути.


    А есть какие-то шутки, которые людям сложно понимать с нуля?


    Они такие же, как в других языках. Людям сложно понимать вложенные циклы, рекурсию, референсы. Это известная тема, неважно, на каком языке ты программируешь. Обучение человека с нуля — это некое искусство, там есть сложные моменты, которые надо адекватно объяснять. Но в этом плане Kotlin очень хорош. Например, мы получаем фидбэк от университетов. Если взять базовый курс программирования в каком-нибудь ВУЗе и посмотреть, чему их учат, окажется, что никто не учит сразу классам. Обычно учат простым процедурным вещам: как писать циклы, как писать функции вызывать и так далее. Раньше многие учили на C++, потом ринулись в Java, теперь на Python. Но не каждый язык одинаково хорош. Та же Java была когда-то очень популярна, и до сих пор многие вводные курсы читают на Java, но это не самый лучший язык именно для вводного курса программирования. Чтобы написать простой хэлловорлд, нужно написать класс, puiblic static void main… А ты же учишь не объектно-ориентированному, а процедурному программированию. В Java, чтобы что угодно написать, надо объявлять класс. Зачем новичку парить мозг?

    Kotlin в этом плане больше подходит для обучения: открываешь файл, пишешь функции свои, почти как в Python, только с типами. По сравнению с C++ можно поспорить, потому что C++ очень большой язык. Это не значит, что на плюсах нельзя учить программистов, можно. Но когда учат на плюсах, то учат очень ограниченному подмножеству. Рассказывают небольшие фишки, очень аккуратно, чтобы обучающийся не сделал шаг влево и шаг вправо. А Kotlin — хороший типизированный язык. Если хочешь хорошему нетипизированному языку научить — это Python. Я свою дочку в качестве первого языка программирования научил Питону. Чтобы не забивать ей голову типами сразу. Когда ты вообще не умеешь программировать, тебе нужно очень много узнать. Не бывает так, чтобы ты сразу всё узнал. Нужно постепенно учить. Поэтому проще вначале научить всяким императивным конструкциям — циклы, ввод, вывод, функции, процедуры — а типы отложить пока в сторонку. Следующий же язык должен быть типизированным, чтобы разобраться с типами.


    То есть ты считаешь, что типизация — полезная штука?


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


    А как джаваскриптеры живут?


    Ну как живут… подевелопили проект, бросили, пошли к следующему. Так и живут. Большинство JS-проектов — очень маленькие. Сделал — и всё. Есть большие проекты на JS, но там не живут, а страдают. И если кто-то пытается делать большой проект на JS, скорей всего переходит на type checkers, на Flow или TypeScript. Что-то большое поддерживать на JS сложно именно по причине отсуствия типов. То же самое с Python. Пока это какие-то шаблонные DSLки, всё отлично. Ты можешь на Django зафигачить огромный проект, если ты сильно не изобретаешь велосипед. Пишешь джанговские классики, пользуешься обычными механизмами. Когда я говорил про сложность проекта в строчках, это, конечно же, обман. Сложность не определяется строчками кода. Я могу иметь огромнейший сайт на Django, где огромное количество строчек кода, десять тысяч формочек и CRUD-страниц. Но так как все они одинаково нафигачены, бизнес-логики нет — это всё легко и понятно. Но как только я начну писать сложную бизнес-логику, делать какую-то иерархию классов, моделировать сложный домен, то в нетипизированном языке я очень быстро умру. Очень быстро. И всё это поддерживать будет невозможно. На этом строится философия Kotlin, он еще более типизированный, чем Java, еще более строгий. Это core belief в команде Kotlin, что язык промышленного масштаба должен быть строго типизированным.


    Правильно понимаю, что если есть какой-то большой сложный фронтенд, то даже там Kotlin уже имеет смысл?


    Конечно! Если фронтенд большой и сложный, значит, в нем вряд ли просто какие-то CRUD-странички. Опять же нужно различать: если у тебя большой и сложный сайт в котором много страниц, и все страницы одинаковые, и логики там нет — вьюшки, то это одно. А вот если это большое сложное веб-приложение, в нем много сложной логики — конечно, нужен типизированный язык. Это объясняет, почему TypeScript и Flow набирают популярность — это типизированные штуки поверх JS.


    Про Kotlin есть еще момент, что Kotlin для JS не может победить TypeScript просто так, один на один. Если ты будешь сравнивать разработку веба на TS и Kotlin/JS, естественно, TS победит, потому что он заточен под JS-экосистему, он прямо для нее создан. Зато Kotlin/JS победит, если тебе надо этот код шарить. Можешь и на фронтенд скомпилировать, и на бэкенд.


    И там будут всякие заточки, о которых ты говорил — вроде конвертирования double…


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


    Когда-то ты был связан с олимпиадным программированием.


    Я до сих пор связан.


    А чем ты занимаешься?


    Провожу олимпиады :-) И сам участвую, но редко.


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


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


    А на Java сложней выиграть, есть какие-то скрытые оверхеды?


    Зависит от соревнования. В нормальном соревновании — одинаково, если в нем задачи больше на идею и правильный алгоритм. Но бывает какая-нибудь дичь, когда задачи подразумевают неасимптотическую оптимизацию, где надо всё до такта оптимизировать — там, конечно, на Java будет тяжело, придется много стараться. Плюс бывает очень маленькое время выполнения теста. Грубо говоря, если у тебя ограничение по времени исполнения несколько секунд, то HotSpot за секунду прогревается на небольшом коде и пофиг. А если у тебя лимит на все — секунда, то на Java ты можешь проиграть просто за счет того, что пока HotSpot разогревается и компилируется — уже секунда прошла.

    Да, бывают дикие соревнования, где на Java тяжело. Но нормальные соревнования (популярные, поддерживаемые хорошими людьми) — там стараются сделат задачи и окружение так, чтобы на Java и на плюсах были одинаковые шансы. И причины понятны: хоть Java и не растет в образовании, но и сильно никуда не убывает. Где-то некоторые вузы отказались учить Java и перешли на Python — и из-за этого, в том числе, сейчас многие соревнования научились Python. Это такой стабильный третий язык из поддерживаемых. Соревнования, в основном, студенческие. Есть и профессиональные соревнования, и большие компании делают что-то вроде Facebook Hacker Cup, где может каждый участвовать, но всё равно, основная тема в спортивном программировании — школьная и студенческая. В школьные и студенческие годы люди будут постоянно выступать и тренироваться. Но после выпуска из ВУЗа, после выхода на работу — очень мало людей будут продолжать участвовать. Поэтому выбор языков определяется тем, что используют в образовании. Если учат плюсам, яве и питону, то и на соревнованиях будут они. Для многих программистов Java — первый язык, соответственно, все соревнования стараются поддерживать Java. Ради соревнований учить С++ — дичь. Он для системного программирования, низкоуровневого программирования, тебе не нужно иметь миллион C++-программистов, это бессмысленно совершенно.


    А как тебе идея — добавить Kotlin в список стандартных языков?


    Ну вот, собственно, эту идею мы активно и продвигаем. Есть ICPC, который ежегодно проходит, собирает сотни тысяч участников по всему миру, больше сотни команд проходит в финал. В ICPC Kotlin поддерживается. Сейчас там список языков такой: C/C++, Java, Python и Kotlin. Но пока, естественно, на нем никто особо не пишет, по причине вот какой проблемы: проникновение в образование еще на очень раннем этапе. На студенческих соревнованиях используются те языки, которым студентов учат.


    А где-нибудь уже учат Kotlin?


    Где-то точно учат. Например, в Питерском Политехе. Но мы пока на очень раннем этапе, на «шаге 0» этого процесса.


    Там нет каких-нибудь фатальных недостатков?


    Нет, для начального образования Kotlin лучше, чем остальные языки. Просто образование — консервативное. У людей есть готовая программа, учебники. Никто не любит изменений. Зачем профессор, который учит на первом курсе студентов программированию, будет менять язык, в чем бонус? Это может раз в десять лет пересматриваться.


    Бонус, например, в том, что человек, который оттуда выйдет, будет более приспособлен к действительности.


    Нет. Потому что не так важно, какой язык ты изучил первым. Профессиональный программист за свою жизнь изучает десяток языков и использует около трех языков активно. Плюс всё это постоянно меняется. Чему тебя научат программировать первым — не так важно. Важно, какой ты багаж языков имеешь по выпуску из вуза — это другая тема, это важно. И здесь мы сталкиваемся с проблемами на консервативных рынках, которые ориентированы на авторитет. Например, в Китае существует проблема, которая выясняется после общения с ребятами оттуда. Берешь какую-нибудь крупную контору, в которой много программистов, спрашиваешь — почему вы не используете Котлин? А потому что вот, не учили ребят Котлину в ВУЗе, и они ничего нового не хотят изучать, а зачем им?


    А у нас не так?


    Это всюду так, просто в разном масштабе. В разных культурах по-разному. Есть культуры, в которых как гуру сказал, или как учитель сказал — так и будешь делать. Где-то люди более самостоятельные, больше склонны к экспериментированию, инновациям. Где-то люди пойдут и сами все изучат. Где-то пальцем не пошевелят и будут делать ровно то, чему их научили. В России внедрений Kotlin больше, но это и потому еще, что мы изначально отсюда, больше на конференциях выступаем и так далее.


    Это в моем поколении программисты были энтузиастами. Я вырос, когда программировали те, кому это нравилось, они всё изучали самостоятельно, потому что ничего не было. А сейчас это массовая штука, которой учат. Возьми современного программиста, большая часть делает это не потому что любит, а потому что этому научили и теперь платят много денег. Соответственно, такие люди не будут изучать технологию, которая только что вышла. Зачем им?


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


    Нет, конечно! На Котлине ты, скорее, получишь больше удовольствия.


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


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


    Это как-то очень уныло, если не ужасно.


    Это правда жизни, к сожалению. Какой бы ужасной она ни была. И таким людям, конечно, все равно. Kotlin, не Kotlin.


    Насколько понимаю, в JetBrains как раз очень многие работают потому, что им нравится работать.


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


    Наше время потихоньку подходит к концу, поэтому такой вопрос: можешь ли ты передать что-нибудь нашим читателям на Хабре? Какое-нибудь напутствие, какое-нибудь откровение?


    Могу передать пламенный привет :-) А откровения никакого не скажу, какое может быть откровение? Единственный вывод, который можно сделать из нашего разговора: от работы счастлив тот, кто получает удовольствие. Я читал несколько блогов хороших людей, которые программировали на Джаве, просто работали, не получая никакого удовольствия. А потом по каким-то причинам им стало любопытно, жизнь заставила, они попробовали Kotlin, и неожиданно для себя открыли, что от работы можно получать удовольствие. Что можно любить то, что ты делаешь. Что можно любить язык программирования. А не просто использовать, безэмоционально, как некий инструмент. Конечно, язык — это некий инструмент, но можно относиться к нему опосредованно, а можно его любить. Это разное отношение, в том числе разное отношение к работе создает.

    К Kotlin очень много людей испытывают теплые чувства, сравнимые с любовью, именно потому, что на Kotlin просто приятно программировать, особенно после Java. Может даже, не только после Java. Наверное, нет языков, на которых настолько приятно (именно такое слово) программировать. Есть языки с большей функциональностью, с более сильными фичами, есть языки с более строгой системой типов, есть языки, где все pure, есть, где всё наоборот — unsafe. Возьми любое измерение, и найдешь языки, которые в этом свойстве круче Kotlin. Но в Kotlin такой баланс, что неспроста он на StackOverflow в опросе этого года оказался вторым в топе most loved languages. Первым, кажется, стал Rust. Но Rust нам не конкурент, потому что Rust — язык системного программирования. Мы в эту нишу не лезем. Нисколько не обидно, что Rust в этом плане обогнал Kotlin. Мы боремся, чтобы Kotlin стал основным языком для прикладного программирования, на котором приятно решать прикладные задачи. Некоторых фичей Rust у нас нет и никогда не будет, потому что они просто не нужны прикладному программисту. Не должен он вручную управлять памятью или думать о тонкостях владения, прикладной программист должен решать бизнес-задачи. Он должен свой домен трансформировать в код. И это должно быть максимально прямое преобразование без каких-либо мешающих ему факторов. Мы пытаемся эти мешающие факторы устранить. Чтобы ты свою бизнес-задачу максимально прямо, без воды и лишнего кода преобразовывал в решение.


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


    Естественно, Kotlin учитывает опыт предшественников. Как и любой современный язык. Это и есть прогресс — когда что-то новое создается с учетом старых недостатков. Неспроста же в Kotlin сделаны nullable-типы. Ну что далеко ходить, возьми любой энтерпрайз, пойти в любую крупную контору, посмотри их крэш-логи, и увидишь, что самый частый exception — NullPointerException. Это известный факт, и если ты делаешь новый язык — нужно ее решать. Поэтому мы очень много внимания в языке уделяем nullability. И так далее. Если ты дизайнишь язык не абстрактно, не как академическое упражнение, а пытаешься решить проблемы людей, с которыми они сталкиваются часто, то язык получается хорошим. Почему его любят? Потому что он решает их проблемы.

    JUG Ru Group
    350.52
    Конференции для программистов и сочувствующих. 18+
    Share post

    Comments 108

      0
      замечательная штука котлин(и корутины в нем прекрасны).
      но вот я так и не пойму — у меня руки кривые или особенность:
      весьма миниатюрный сервис(условно примитивный рест) может жрать 20 метров оперативки, а может 200. такое ощущение, что зависит от фазы луны в момент компиляции.
      может кто что подскажет?
        +4
        JVM? GC?
          +1
          да в том то и дело что при прочих равных.
          пускаю тестовую нагрузку. одинаковую.
            +4
            Так кто вам мешает посмотреть, на что память расходуется?
              +1
              да в том то и дело, что просто хип раздулся и назад не отдается(тут ничего удивительного).
              не пойму причину, почему иногда раздувается, а иногда нет.
                +1
                GC — сволочь ленивая. Пока JVM не подберётся к верхнему пределу — GC ничего собирать не будет. Попробуй поставить верхний лимит поменьше.
                  +1
                  пробовал, конечно.
                  так оно тогда иногда падает :)
                  тоесть, в принципе, оно иногда и надо вроде много памяти. но хотелось бы чтоб освобождалось.
                  • UFO just landed and posted this here
                      +1
                      ну, естественно, иногда возможны всплески потребления.
                      но нечасто
          +3
          А как тебе подскажут без исходников?
            +4
            JVM любит память. Очень.
            Любое приложение на JVM рано или поздно съест всю память, выделенную под хип, и даже не будет возвращать её обратно системе, просто потому, что так работает GC (по крайней мере, те, которые сейчас в Hotspot JVM).
            • UFO just landed and posted this here
                +1
                там выше правильную мысль сказали.
                не отдает память назад, зараза.
              +3
              Очень странно что Rust отнесли к системным языкам. Да, он позволяет писать на низком уровне, но то, что он это позволяет, вовсе не обозначает, что это основное его применение. Он включает в себя довольно много функционального, что (ИМХО) является одним из признаков прикладного языка, хотя и имеет сложности например с композицией функций.
                +4

                Тут я могу процитировать rust-lang.org "Rust is a systems programming language that runs blazingly fast, prevents segfaults, and guarantees thread safety.", но вообще-то из без этой цитаты Rust очевидно является системным языком по своему дизайну и набору функционала.

                  +3
                  очевидно


                  Когда встречается эта фраза, сразу становится понятно, что оснований для заявления особых нет.

                  «Rust is a systems programming language that runs blazingly fast, prevents segfaults, and guarantees thread safety.»,

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

                  Я подумываю написать статью о сравнении с тем же C#, постараюсь найти время в ближайшее время.
                    +3

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

                      0
                      На расте — достаточно удобно. Например, лично я не вижу особых отличий от, например, C#. С которым я знаком, и который как по мне трудно обвинить в низкоуровневости и неудобстве.
                        +1

                        Лайфтаймы против GC — тут GC конечно сильно удобнее при написании кода.

                          +1
                          Про это я тоже собираюсь написать — и про эпические костыли в виде IDisposable, и все остальное…
                          0
                          Почему в низкоуровневости надо «обвинять»? Это крутое свойство языка, позволяющее ему таргетироваться на определенную нишу
                            +1
                            Многие считают это приговором языку в плане продуктивности, в том плане, что низкоуровенвый == нельзя быстро нафигарить какой-нибудь веб-сервер.
                              0

                              Зачем на системных языках быстро нафигаривать веб-сервер?) Берешь котлин, фигаришь. А на системном языке решаешь системные задачи. Тот же веб-сервер, но маниакально перфомансно, очень долго, очень дорого и невероятно офигенно. А потом оборачиваешь в нативную обёртку и зовёшь его из Котлина, чтобы получить лучшее из двух миров одновременно

                                +1
                                Вот это то, о чем я и говорю.

                                Мое мнение, что на расте можно сделать веб-сервер за то же время и с тем же удобством, то на C#/Kotlin/…
                                  0
                                  Написать не просто серверный движок, а целый сервис вместе с бизнес-логикой? Ну это вряд ли. На то и ниши разные, решать прикладные задачи на растения, ровно как и на C/C++ — упаси
                                    +1
                                    Написать не просто серверный движок, а целый сервис вместе с бизнес-логикой? Ну это вряд ли.

                                    Вот об этом я и говорю.

                                    Написать веб-сервер на каком-нибудь actix-web не сложнее, чем на ASP.Net Core или там джанго.

                                    extern crate actix_web;
                                    use actix_web::{http, server, App, Path, Responder};
                                    
                                    fn index(info: Path<(u32, String)>) -> impl Responder {
                                        format!("Hello {}! id:{}", info.1, info.0)
                                    }
                                    
                                    fn main() {
                                        server::new(
                                            || App::new()
                                                .route("/{id}/{name}/index.html", http::Method::GET, index))
                                            .bind("127.0.0.1:8080").unwrap()
                                            .run();
                                    }

                                    Вот что тут низкоуровнего? Я не понимаю. На б0льших размерах проекта сложнее ничего не становится. Как и во взрослых языках, есть устоявшиеся джентельменские наборы вроде actix+diesel+r2d2, которые позволяют описать стандартный вебсервер с ORM персистент слоем.
                                      +1
                                      Это пример уровня Hello World.

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

                                      Почему я не выберу rust для enterprise:

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

                                      Далее, AOP. Это бесценно повесить над бизнес-функцией атрибут [InTransaction], чтобы AOP-фреймворк сгенерировал транзакцию при входе, commit при выходе и rollback при исключениях, а также учёл любую вложенность таких ф-ций.

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

                                      Далее, dependency injection. Иметь интерфейс и кучу реализаций очень важно для генерации прокси. На клиенте интерфейс, пусть для конкретики будет IShopService, привязан к веб-клиенту, который сходит по HTTP и вызовет метод. Не меняя клиентский код, его можно протестировать, заменив привязку интерфейса с web-клиента на реальный сервис, или на mock-объект. На сервере же IShopService выставлен наружу, но web-обёртка вызывает реализацию MyShopService, находя её по интерфейсу в di-контейнере.

                                      Напоследок, динамическая кодогенерация. Чтобы всё из предыдущего пункта не писать руками (напоминаю, сервисов таких сотни), есть способ сказать фреймвоку «найди интерфейсы, отмеченные атрибутом, и каждому нагенери веб-сервер, который делегирует реализацию методов классу, который зарегистрирован в di-контейнере для этого интерфейса».
                                        0
                                        Это пример уровня Hello World.

                                        Ну возьмите не хелло ворлд, например exonum или semaphore. У меня не было задачи в комментарии описать бизнес-сценарии на сотни kloc.

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

                                        Это плюс, а не минус. Бросил исключение, а его никто не обрабатывает, упс. В сочетании например с тасками, которые бросают исключения, и которые потом нужно хитроумно доставать не лучшая затея (я про TaskScheduler.UnobservedTaskException и прочую хрень). Короче, вопрос исключительно в «я привык к исключениям». Result как нормальный АДТ в разы лучше и старых-добрых «кодов ошибок», и исключений, я лично проверял. Рекомендую тоже самому проверит, прежде чем описывать преимущества одного из подходом.

                                        Далее, AOP. Это бесценно повесить над бизнес-функцией атрибут [InTransaction], чтобы AOP-фреймворк сгенерировал транзакцию при входе, commit при выходе и rollback при исключениях, а также учёл любую вложенность таких ф-ций.

                                        derive макрос.

                                        #[repr(C)]
                                        #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
                                        pub enum LineType {
                                            /// Default type
                                            Filled = -1,
                                            /// 4-connected line
                                            Line4 = 4,
                                            /// 8-connected line
                                            Line8 = 8,
                                            /// antialiased line
                                            LineAA = 16,
                                        }


                                        Далее, dependency injection. Иметь интерфейс и кучу реализаций очень важно для генерации прокси.

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

                                        Напоследок, динамическая кодогенерация. Чтобы всё из предыдущего пункта не писать руками (напоминаю, сервисов таких сотни), есть способ сказать фреймвоку «найди интерфейсы, отмеченные атрибутом, и каждому нагенери веб-сервер, который делегирует реализацию методов классу, который зарегистрирован в di-контейнере для этого интерфейса».

                                        Макросы, обычные и процедурные, умеют все, что нужно. Да, в рантайме погенерить не получится, но это тоже в минусы я бы заносить не стал. Например, я сейчас всю свою рантайм-кодогенерацию переписываю на Roslyn (в статье описано более детально), как раз чтобы в compile time видеть все, что происходит. Это дает сразу кучу преимуществ, начиная с производительности (даже первых запусков), заканчивая отсутствием необходимости в рантайм-зависимостях (private assets для генерации, в рантайме они требоваться не будут.).
                                          +1
                                          derive макрос.
                                          Он как-то поможет залогировать входные параметры ф-ции и результат?

                                          Например, есть бизнес-функция
                                          int sum(int a, int b)

                                          Что бы такое придумать, чтобы с минимальным синтаксисом добавить логгер?
                                            +1
                                            Можно делать любую кодогенерацию, в том числе и добавить логгирование. Хотя я лично всегда предпочитаю явные логгирования в теле функции, нежели использование всяких Fody и т.п. Звучит неплохо, на практике не хватает контекста. Я с удовольствием пользуюсь serilog и эластиком, АОП туда натянуть трудно, как мне кажется.
                                              +1
                                              Можно делать любую кодогенерацию
                                              Каким образом? Нужно же не только код нагенерить, но и распарсить уже существующий код (например, узнать типы/наименования параметров функции).
                                                +1
                                                Вы получаете на вход TokenStream, можете его обрабатывать как угодно, и анализировать как угодно.
                                                  0
                                                  Токены это слишком низкоуровнево.

                                                  С другой стороны, как это интегрируется в IDE? Можно ли будет дебажить свои функции, которые переколбасил кодогенератор, обернув их логгированием?
                                                    0

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


                                                    Насчет низкоуровенвости — джентельменский набор крейтов syn и quote помогают писать более высокоуровнево.

                                                      0
                                                      Тогда это сыро. Кодогенераторы AOP-фреймворков в .net позволяют дебажить изменённый код. Единственная разница — при входе/выходе в фунцию по «Step Into» можно войти не в функцию, а в код обёртки (что тоже неплохо).
                                                        0
                                                        Кодогенератор Roslyn не позволяют этого. Всё это в зачаточном состоянии есть на уровне пропозла. Ну а про проблемы рерайтов байткода можно почитать в тех же статьях про Code Contracts.
                                                          0
                                                          Контрактам требуется намного более глубокий рерайт нежели для AOP.
                                            +1
                                            В расте более функциональный подход. DI вообще сам по себе довольно плохая штука. В шарпах без него никуда, в других языках можно жить лучше.

                                            Возможно, не хватает хорошей статьи, какие best practices есть rust для enterprise-приложений. В трёхзвенке на java/c# сейчас всё хорошо изучено, как делать сервисы и компоненты, чтобы удобно тестировать и проксировать.
                                              +1
                                              Слишком сыро сейчас, чтобы писать подобные штуки. Хотя, конечно, хотелось бы. Все же слишком bleeding edge. Но вот небольшие и средних размеров сервисы уже делать можно. Через год-два можно будет массово кровавый энтерпрайз делать.
                                                +2
                                                Через год-два можно будет массово кровавый энтерпрайз делать

                                                Это все здорово конечно, только кто будет «массово делать»? И зачем заказчикам связываться с проектами на расте, которые определенно будут дороже?
                                                  +2
                                                  Дороже чего? Написать сложную систему на расте может же быть дешевле, по причитам все той же надежности. Нет боли с многопотоком (а любой крупный сервис имеет больше одного потока), не боли с nullref, нет проблем с ошибками, которые забыли обработать… Если для прототипирования раст подходит плохо, то для строительства сложного дорого комплекса и работе на перспективу он может быть значительно дешевле какого-нибудь C#/Java/Kotlin/Go.
                                                    0
                                                    для строительства сложного дорого комплекса и работе на перспективу он может быть значительно дешевле какого-нибудь C#/Java/Kotlin/Go.

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

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

                                                      –1
                                                      Дороже чего? Написать сложную систему на расте может же быть дешевле, по причитам все той же надежности
                                                      Я говорю о таких «enterprise» проектах, где 100500 разных CRUD, перемешанных обильно с «бизнес-логикой». Чтобы выходило дешевле, уровень подготовки программистов нужен невысокий (а у C++ и Rust с этим беда). Лучше, чтобы они не запаривались с «владением», а GC за ними всё убирал.
                                                        +1
                                                        Проблема с GC, что кроме памяти у нас есть и другие ресурсы (коннекшны к базе, например) а их GC убирать не умеет.

                                                        Так что чем более строгий компилятор, тем меньше багов попадает в прод в принципе. Как раз-таки строгий компилятор не пропускает ошибки джунов в прод, вместо UB/nullref в рантайме на продакшне. Компилятор компенсирует незнание программистом тонких моментов, если перефразировать.
                                                          –1
                                                          Коннекты в пуле и закрывать специально их не надо. А с транзакциями хорошо справляется AOP. Навесил атрибут на функцию — и при выходе транзакция закончится обязательно. Это покрывает 99% случаев.

                                                          Как альтернатива — IDisposable и using() { }
                                                          Для «enterprise»-кода больше и не нужно.
                                                            0
                                                            Ну вот IDisposable это и есть «вызывал Close в конце. Не вызывал? ССЗБ». Это вынужденная концепция ака «костыль», т.к. нам нужно детерменированно закрыть ресурс, который плохо ложится на гц который когда-нибудь там закроет.

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

                                                              Я всё-таки думаю, вопрос в том, что сейчас считается системным программированием, а что прикладным. Не изменились ли со временем критерии?

                                                              Браузер, например, всегда считался прикладной программой. Те, кто говорит, что «rust — не для прикладного программирования», что они скажут насчёт такого проекта, как браузер? Lifetime-ы его объектов очень сложные, тут, я думаю, имеет смысл использовать rust.
                                                +1
                                                Это плюс, а не минус. Бросил исключение, а его никто не обрабатывает, упс.
                                                Это забота платформы. Никто не обрабатывает — ошибка 500 HTTP-сервера, либо можно написать обработчик, чтобы предоставить вызывающей стороне детали из Exception.Message, залогировать опять же единообразно. У меня, например, нет идей, как в расте получить понятный читаемый лог ошибок единообразным способом, если в типе result<T,E> тип E может в принципе быть вообще любым. Да и стектрейс в логе ошибок видеть очень уж удобно.
                                                  +1

                                                  Всегда можно сделать Box<dyn Error> и использовать тот же апи, что и в шарпе. А вообще для удобной работы с ошибками есть failure крейт. Особых проблем не замечено.

                                                    +1
                                                    Если сторонняя библиотека возвращает Result<int, &'static str>, то в этот формат конвертировать вручную на каждом вызове?
                                                      +1

                                                      Всегда можно сделать impl From<&'static str> for MyError Type и писать на функции сторонней библиотеки let foo = external_lib_call()?;

                                                  +2
                                                  Так потому и не обрабатывается, что мне не хочется его обрабатывать. Не хочется тратить на это время. Если кто-то выше по стеку потратил на это время — хорошо, нет — ну упадет все с ошибкой в гуях веб-приложения например, «произошла неизвестная ошибка» или что-то такое.

                                                  Я не хочу обрабатывать того, чего я не хочу.

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

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

                                                    А checked exceptions кстати получили продолжение в виде эффектов, которые не только исключения помечают, а вообще любые побочки. И люди этим даже пользуются.
                                              +1
                                              Выше я написал: «Rust — не относится к системным»
                                              Чуть ниже: «А что плохого что язык системный?»
                                              И ещё чуть ниже: «Писать веб-сервер на системном расте?!?»

                                              мне кажется это вполне доказательство что метка исключительно «системный» плохо влияет на восприятие языка как прикладного, которым раст вполне является, уж что-что, а веб-сервер на rocket делается легко: rocket.rs/guide/getting-started

                                              + он вылез в топ StackOverflow врядли исключительно по системным вопросам.

                                  +3
                                  Rust is a systems programming language that runs blazingly fast, prevents segfaults, and guarantees thread safety.

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

                                  Это надо понимать ровно так, как это написано. Авторы языка Rust считают его языком системного программирования. Это как бы намекает, что существующий функционал языка и планы по его развитию заточены под задачи стоящие перед системными программистами. Я написал "очевидно", потому что мое понимание о том, что такое язык системного программирования и что он должен уметь, похоже, сильно совпадает с пониманием этого вопроса авторами языка Rust. Когда я первый раз почитал tutorial по языку Rust (но не видел этой фразы) и увидел набор его фич, то я сразу подумал "какой крутой язык для системного программирования — у него есть все шансы победить С/C++".

                                    +1
                                    Авторы языка Rust — это не монолит, который имеет свое мнение. Да, у раста есть шансы подвинуть плюсы. Но это не значит, что ему заказан путь в наколеночные веб серверы и кровавый энтерпрайз.

                                    Я написал «очевидно», потому что мое понимание о том, что такое язык системного программирования и что он должен уметь, похоже, сильно совпадает с пониманием этого вопроса авторами языка Rust

                                    Самые крупные фичи, которые ожидаются до конца года — это упрощение модульной системы, async/await, улучшение футур и специализация (нужна для применения ООП паттернов, и не только). Всё это не особо нужные для «системного программирования», зато нужные для этого вещи (стабилизация lang_items, более полноценная поддержка no_std,… ) на втором плане. Как помне, тренд на веб довольно заметный.
                                  +1
                                  Если не вдаваться в данную фразу, как вы думаете — возможно ли оказаться в top'е StackOverflow с исключительно «системным» подходом?

                                    +1

                                    Конечено можно. C++ один из ведущих языков по количеству пользователей и Rust дает для этих программистов надежду на светлое будущее и освобождение от оков legacy. Будь я программистом на C++ я бы тоже любил Rust. Да и вообще для задач где важно четкий контроль за ресурсами и производительностью Rust рулит. Но вот если решаешь чисто бизнес-задачу, то голова занята другим. Не хочется думать о том, надо передавать объект по значению, по ссылке или в коробке, какой у него лайфтайм и т.п. Язык прикладного программирования не должен отвлекать программиста от его бизнес-задачи на эти мелочи.

                                    +1
                                    Ребята в веб хотят www.arewewebyet.org
                                  +1
                                  Вот, кстати, когда меня спрашивают «почему Kotlin?», то одним из первых аргументов отвечаю, что на нем приятно писать. :)
                                    +1
                                    На js-target до сих пор нет нормальной рефлексии…
                                      0

                                      Воспользуюсь случаем спросить для сбора данных. А какой у вас use-case для рефлексии? Для чего вы её используете (если для разного, то расскажи в порядке важности, пожалуйста)?

                                        0
                                        Use-case такой: сериализация идет в строго типизированную структуру данных, которую потом можно редактировать своим универсальным редактором (что-то вроде иерархического object-inspector). Формат может быть любой, (например JSON или бинарный). Сначала идет описание структуры (с возможностью пропускать), потом данные. К объектам это применяется (читается и пишется) через промежуточное представление, ориентируясь на имя и тип поля. Это позволяет гибко добавлять/удалять поля класса во время разработки (за номерами версий в файле следить не нужно). Чуть не забыл, в классе поля для сериализации нужны далеко не все.
                                          0
                                          Рефлекшн нужен для всего, начиная с кодогенерации, и заканчивая сериализацией. Типичный пример.
                                            0

                                            Про сериализацию знаем. Делаем так, чтобы можно было сериализовывать из/в любых форматов (в т.ч. в БД) без рефлексии, то есть очень быстро и абсолютно type-safe на любой платформе (JVM/JS/Native). Следите за проектом https://github.com/Kotlin/kotlinx.serialization


                                            А какого рода кодогенерация интересует?

                                              0
                                              А какого рода кодогенерация интересует?


                                              Ну например в стиле сваггера (линк).
                                                0

                                                А что мешает делать это сейчас? Берете какой-нибудь Kotlin Poet и генерируете нужные вам исходники. Генератор запускаете на JVM, на нем с помощью рефлексии можете пройтись по вашей модели данных и всё узнать. Результирующий код будет работать где угодно — на JVM, JS, и Native.

                                                  0
                                                  с помощью рефлексии

                                                  По-моему про это речь и шла ведь.
                                                    +2

                                                    Речь шла про то, что рефлексии нет на Kotlin/JS, только не понятно зачем она там нужно. Source generator будет же работать на Kotlin/JVM (где рефлексия есть) и полученный код может работать на Kotlin/JS без всякой рефлексии.

                                                      0
                                                      А, в таком случае я неправильно понял, о чем речь. Спасибо за объяснение.
                                              0
                                              Ну да, на С# все получилось как надо…
                                                0
                                                А что бы на Kotlin сериализовать без рефлексии, надо сначала все переложить в промежуточную структуру, а это дополнительный код с возможными опечатками. Ну там еще есть вариант с инстанциированием воспомогательного объекта и использованием массива вида [MyClass::field_a, MyClass::field_b...], что тоже не фонтан…
                                                  0

                                                  Вот здесь не понял. Можете пояснить на примере? Буду очень признателен если дадите ссылку на issue.

                                                    0
                                                    Это не issue, а скорее crutch:
                                                    class MyClass : Slzr
                                                    {
                                                        companion object static
                                                        {
                                                            val slzr : Array<Any?> = arrayOf<Any?>(::MyClass, MyClass::class.simpleName, 7, arrayOf<Any>(MyClass::br, MyClass::fr))
                                                        }
                                                        var br = 777;
                                                        var fr = "rrrr";
                                                        override fun get_Slzr_Decl() : Array<Any?> = slzr;
                                                        fun reg_internal(vararg p: Any)
                                                        {
                                                            // for(i in p.indices)
                                                            for(i in 0 until p.size)
                                                            {
                                                                if(p[i] is KMutableProperty0<*>)
                                                                {
                                                                    var pi = p[i] as KMutableProperty0<*>;
                                                                    // var v = pi.get();
                                                                    if(pi.get() is Int)
                                                                    {
                                                                        @Suppress("UNCHECKED_CAST")
                                                                        pi = p[i] as KMutableProperty0<Int>;
                                                                        pi.set(111);
                                                                        // v = pi.get();
                                                                        print("Int:\t");
                                                                    }
                                                                    else
                                                                    if(pi.get() is String)
                                                                        print("String:\t");
                                                                    println("p[" + i + "] = " + pi.name + " -->" + pi.get());
                                                                }
                                                                else println("p[" + i + "]")
                                                            }
                                                        }
                                                    
                                                      0

                                                      Извините, но я ничего не понял. Во-первых форматирование кода настолько далеко от Kotlin Code Style (см. https://kotlinlang.org/docs/reference/coding-conventions.html ) что мне очень тяже через него продраться… и вообще что он призван проиллюстрировать? Зачем вот это всё? В чем смысла этого кода? Какую задачу вы пытаетесь решить?

                                                        0
                                                        Ай, поторопился, извините. Не та функция. Давайте попробуем еще раз с пояснениями. Класс для сериализации:
                                                        interface Slzr {
                                                            fun get_Slzr_Decl() : Array<Any?>
                                                        }
                                                        class MyClass : Slzr {
                                                            companion object static {
                                                                val slzr : Array<Any?> = arrayOf<Any?>(::MyClass, MyClass::class.simpleName, 7, arrayOf<Any>(MyClass::br, MyClass::fr))
                                                            }
                                                            var br = 777;
                                                            var fr = "rrrr";
                                                            override fun get_Slzr_Decl() : Array<Any?> = slzr;
                                                        }
                                                        

                                                        Сериалайзер обнюхивает этот класс примерно так:
                                                            var t : MyClass = MyClass()
                                                            var sd = t.get_Slzr_Decl();
                                                            @Suppress("UNCHECKED_CAST")
                                                            var obj = (sd[0] as KFunction0<Any>)();
                                                            var a : Array<*>? = sd[3] as? Array<*>;
                                                            if(null!=a) {
                                                                for(i in a.indices) {
                                                                    if(a[i] is KMutableProperty1<*,*>) {
                                                                        @Suppress("UNCHECKED_CAST")
                                                                        var pi = a[i] as? KMutableProperty1<Any, *>;
                                                                        if(null!=pi)
                                                                            println("member: " + pi.name + " = " + pi.get(t as Any));
                                                                    }
                                                                }
                                                            }
                                                        

                                                        Здесь просто печатается имя и значение полей заданных для сериализации, но в реальной жизни парсер генерирует вспомогательные данные (HashMap с именами) для ускорения процессов save/load из промежуточного представления сериализуемых данных, кеширауя их в статическое поле slzr. Ну и вспомогательная переменная t должна инстанциироваться не так явно, а через тип. Надеюсь так чуть понятнее.
                                                          0
                                                          Переменная obj это пример инстанциирования. Ну и target js, т.е. RTTI урезан.
                                                            0

                                                            Данный код печатает на экран значение каждого поля, как я понял. То же самое очень легко делается через kotlinx.serialization (поля, которые не нужно обходить, можно пометить Transient). А в чем конечная цель?

                                                              0
                                                              Да, но мне нужно в структуре файла иметь имя и тип в строковом виде, поскольку там сначала описание структуры а потом данные. Иначе редактор не сможет понять что куда можно добавить (sub-objects, array itmes). Плюс в файлах не нужны версии, при десериализации лишние поля игнорируются, а недостающие остаются default initialized.
                                                                0

                                                                Ну дык и в kotlinx.serialization можно игнорировать "лишние" поля, и default там замечательно прописываются. В чём проблема?

                                                                  0
                                                                  Мне нужно в структуре файла иметь описание всех используемых структур c полями (имена и типы) в строковом виде. Хорошо бы по аннотациям вытягивать доп инфу (например, toltip info).
                                            +1

                                            Насчёт совместимости и raw-типов. Задача: есть старая версия Java-класса, где некий метод возвращает raw List, и новая версия, где List<String>. Могу ли я сделать Котлин-класс, который вызывает этот метод и способен линковаться с любой из этих двух версий? Скажем, я пишу плагин к системе и хочу уметь работать со старой и новой версией системы. На чистой джаве никаких проблем нет, такое изменение не считается ломающим совместимость на уровне исходного кода.

                                              +3

                                              Да. Пишите на Kotlin: JavaClass.getRawList() as List<String>. Будет компилироваться с любой из версий метода getRawList(). Под разной версии будут разные warnings (можно сделать suppress и того и другого, чтобы обе версии компилировались без warnings).

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

                                              Наконец-то! Останется только придумать материалы для опытных программистов, знающих НЕ Джаву. А то пару лет назад пытался понятъ Котлин, но все материалы строились по принципу: "Ну, вы же знаете, как это в Джаве. А у нас почти так же, но вот такие плюшки!"


                                              То есть, чтобы взыться за Котлин, надо было заранее знать Джаву, почти без вариантов


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

                                                +1

                                                Что бы еще ну очень хотелось бы от корутин в Котлине:


                                                • вещь подобная ThreadLocal, которая бы идентифицировала текущий scope корутины, например доступ к CoroutineContext
                                                • более-менее стандартизированная сериализация корутин. Для длительных бизнес-процессов или пользовательского ввода требуется заперсистить состояние корутины в базе данных, чтобы освободить память. Хотелось бы чтобы при деплое новой версии корутины можно было бы подправить ручками сериализированные данные от старой корутины.
                                                • улучшить трассировочную информацию при возникновении исключения. То, что лежит в stackTrace совсем не то, чтобы хотелось иметь в итоге.
                                                  +3
                                                  • Ну вроде как уже давно есть top-level val coroutineContext через который можно в любой момент получить доступ к текущему контексту, плюс недавно мы доделали затаскивание любых threadLocal в контекст через ThreadLocal.asContextElement()


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


                                                  • Уже есть прототип. В 1.3 всё будет. Конечно, дизайн JDK exceptions (Throwable) для этого не очень подходит, но нам вроде удалось получить адекватный вариант.


                                                  +1
                                                  Когда 8 лет назад kotlin появился, он был похож на еще один backend для JVM. Синтаксис и грамматика со своими причудами и ключевыми словами (it, reified и т.п.), местами на пару букв короче чем в Java, местами длиннее. Странные решения, вроде отмены традиционного синтаксиса for(;;). По своему опыту могу сказать что циклы while и do{}while обычно вообще не используются, так как for(;;) самодостаточен. Вместо static members в kotlin классы двух видов: только static (это называется Object) и только не-static. Если в классе нужно и то и другое, встраиваем один класс в другой как companion (ему еще можно задавать имя, случайно не знаете зачем?). Если статический member один, то конструкция выгляди пухловато. Объявление объектов с динамическими полями в стиле JSON выглядит пухловато, в перемешку со строковыми «mutableListOf» и «mutableMapOf». Ну да на самом деле все это мелочи…

                                                  В какой то момент начались таргетинги на разные платформы: JVM, JS, LLVM. Появился соблазн иметь общую кодовую базу, но… Java, JS, C# по отдельности имеют не плохой RTTI (run-time type information), это позволяет писать свои сериализаторы и прочие reflect полезные штуки. В kotlin уже который год видим ошибки вида: «error: unsupported [This reflection API is not supported yet in JavaScript]».

                                                  Я понимаю что не всем это надо, но если проект чуть сложнее чем HelloWorld, то начинаются печали…
                                                    +1

                                                    Планируется ли в Kotlin хоть какая-то поддержка RAII? use { } не в счёт, хотя его и достаточно в самом простом и частом случае.

                                                      0

                                                      Если не хватает use (который работает с наследниками Autocloseable), то всегда можно написать свой аналог.


                                                      inline fun BufferedImage.withGraphics(block: (Graphics2D) -> Unit) {
                                                          val g: Graphics2D = createGraphics()
                                                          try {
                                                              block(g)
                                                          } finally {
                                                              g.dispose()
                                                          }
                                                      }
                                                      
                                                      fun example() {
                                                          val image = BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB)
                                                          image.withGraphics { g ->
                                                              g.drawLine(0, 0, 100, 100)
                                                          }
                                                      }
                                                        0

                                                        Проблема не в том, что use определён только для AutoCloseable, а в том, что как и try-with-resources в Java он спасает только в ситуации, когда надо взять ресурс, что-то сделать с ним и освободить в одном блоке кода.
                                                        Если я хочу например абстрагировать два ресурса, создавая и освобождая их вместе со своим объектом, начинается боль. Вот так сделать нельзя:


                                                        class MyClass : AutoCloseable {
                                                            val resource1 = Resource1(...)
                                                            val resource2 = Resource2(...)
                                                        
                                                            override fun close() = listOf(resource1, resource2).forEach { it.close() }
                                                        }

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

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

                                                        В ближайшем будущем не стоит ожидать в языке Котлин чего-то масштаба RAII в C++ или Lifetimes в Rust. Котлин в настоящее время ориентируется на прикладных программистов, которым редко приходится работать с внешними ресурсами, требующими явного освобождения/закрытия. Для целевой аудитории языка Котлин конструкций языка типа use и её подобных в целом хватает. В сложных случаях неудобно, но приоритеты — есть более актуальные задачи дизайна языка.


                                                        Однако, в Kotlin/Native, который ставит целью удобно интегрироваться с нативной (С) экосистемой, где ручное управление памятью и ресурсами широко распространено, есть дополнительные механизмы в виде арен и memScoped выделение памяти.

                                                          0

                                                          Спасибо за ответ.
                                                          Вот так и получается, что отсутствие GC принуждает иметь средства для управления любыми ресурсами (память, сокеты, файлы и т.п.), а если он есть, то можно ни о чём не думать до встречи с нативными ресурсами, и тут становится неудобно.
                                                          Я согласен, что это редко приходится делать, но иногда, к сожалению, даже в чисто прикладных программах возникает такая необходимость.

                                                            +1

                                                            Да. Всё так. Некоторые языки пытались совместить полностью автоматический GC и ручное управление ресурсами, но успешных примеров (среди популярных языков) в настоящее время нет. Если кто-то что-нибудь на эту тему придумает, то мы с радостью реализуем.

                                                              +1

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

                                                                +1

                                                                Такая попытка была в языке D. В Rust же, напротив, сделали всё верно и не пошли по этому пути. В Rust нет никакого "полностью автоматического управления памятью". Программисту на Rust, как и полагается системному программисту, надо самому думать об управлении памятью и правильно применять всякие аннотации времени жизни объектов и т.п. Отличие языка Rust от C и C++ в том, что Rust из-за этого получается более надежным, так как ошибки работы с памятью (если не использовать unsafe) приведут к ошибке компиляции, а не к падению во время исполнения.

                                                                  +1
                                                                  Под автоматическим я имею ввиду, что разработчик не пишет `delete x` или `file.Close()` в каком-то конкретном месте, а описывает более абстрактно «эта штука должна жить дольше, чем вон та штука». То есть декларативно, что сильно лучше, чем императивно.

                                                                  надо самому думать об управлении памятью и правильно применять всякие аннотации времени жизни объектов и т.п

                                                                  Что мне нравится в расте, что можно на нем писать «несистемно», а именно вообще не исползовать лайфтаймы. Всегда можно ссылку заменить на лишнее копирование. Да, неэффективно. Но зато просто и понятно. А за счет общей хорошей производительности языка можно добиться лучшей производительности при той же понятности, как в «несистемных» языках.
                                                                    0

                                                                    В C++ тоже не надо писать delete или close. Rust развивает эту идею, вынося все опасные операции в unsafe. Это круто. Но дело же не в том, что надо писать. Важно то, о чем программисту надо думать. И в Rust и в C++ программисту надо думать о том чем объекты владеют и какой вообще у того или иного объекта lifetime. В этом и основное отличие современных прикладных языков с полностью автоматическим управлением памяти, что программисту об этом думать не надо вообще. Ни декларативно, ни императивно.

                                                                      0
                                                                      Можете назвать хоть один ЯП где программисту вообще не надо думать какой у какого объекта lifetime?
                                                                        0

                                                                        В порядке убывания популярности по TIOBE: Java, Python, VB.NET, C#, PHP, JS, Ruby, Go, Perl, Dart, F#, Scala и т.д.

                                                                          0
                                                                          На всех этих языках программисту приходится задумываться о том какой у объектов lifetime если они владеют ресурсами.
                                                                            0

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

                                                                              0

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


                                                                              Кстати, сможете ли вы найти ошибку вот в этих строчках (реальный код и довольно популярная ошибка):


                                                                              using (var reader = XmlReader.Create(bytes))
                                                                              {
                                                                                  return Message.CreateMessage(bytes, int.MaxValue, MessageVersion);
                                                                              }
                                                                                0
                                                                                Сложно сказать, т.к. в коде явно ошибка (reader никак не используется), но на заре async/await я постоянно натыкался на ошибку

                                                                                using(var client = new HttpClient())
                                                                                {
                                                                                   return client.GetAsync("www.google.com");
                                                                                }
                                                                                  0
                                                                                  Ой, это опечатка. Там reader вместо bytes должен быть. Но да, ошибки схожи.
                                                        0

                                                        А как у нас с SAST для Kotlin?

                                                        Only users with full accounts can post comments. Log in, please.