Pull to refresh

Comments 108

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

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

очевидно


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

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

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

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

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

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

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

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

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

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

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

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

Написать веб-сервер на каком-нибудь 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 персистент слоем.
Это пример уровня Hello World.

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

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

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

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

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

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

Напоследок, динамическая кодогенерация. Чтобы всё из предыдущего пункта не писать руками (напоминаю, сервисов таких сотни), есть способ сказать фреймвоку «найди интерфейсы, отмеченные атрибутом, и каждому нагенери веб-сервер, который делегирует реализацию методов классу, который зарегистрирован в di-контейнере для этого интерфейса».
Это пример уровня 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 для генерации, в рантайме они требоваться не будут.).
derive макрос.
Он как-то поможет залогировать входные параметры ф-ции и результат?

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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


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

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

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

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

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

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

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

Это не 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 + "]")
        }
    }

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

Ай, поторопился, извините. Не та функция. Давайте попробуем еще раз с пояснениями. Класс для сериализации:
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 должна инстанциироваться не так явно, а через тип. Надеюсь так чуть понятнее.
Переменная obj это пример инстанциирования. Ну и target js, т.е. RTTI урезан.

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

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

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

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

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

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

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

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


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


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

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


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


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


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


Когда 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, то начинаются печали…

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

Если не хватает 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)
    }
}

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


class MyClass : AutoCloseable {
    val resource1 = Resource1(...)
    val resource2 = Resource2(...)

    override fun close() = listOf(resource1, resource2).forEach { it.close() }
}

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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


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


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

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