Комментарии 108
но вот я так и не пойму — у меня руки кривые или особенность:
весьма миниатюрный сервис(условно примитивный рест) может жрать 20 метров оперативки, а может 200. такое ощущение, что зависит от фазы луны в момент компиляции.
может кто что подскажет?
пускаю тестовую нагрузку. одинаковую.
Любое приложение на JVM рано или поздно съест всю память, выделенную под хип, и даже не будет возвращать её обратно системе, просто потому, что так работает GC (по крайней мере, те, которые сейчас в Hotspot JVM).
Тут я могу процитировать 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#, постараюсь найти время в ближайшее время.
Так-то можно писать что угодно на любом тьюринг-полном языке. Вопрос в том, что удобно, а что нет.
Лайфтаймы против GC — тут GC конечно сильно удобнее при написании кода.
Зачем на системных языках быстро нафигаривать веб-сервер?) Берешь котлин, фигаришь. А на системном языке решаешь системные задачи. Тот же веб-сервер, но маниакально перфомансно, очень долго, очень дорого и невероятно офигенно. А потом оборачиваешь в нативную обёртку и зовёшь его из Котлина, чтобы получить лучшее из двух миров одновременно
Мое мнение, что на расте можно сделать веб-сервер за то же время и с тем же удобством, то на C#/Kotlin/…
Написать не просто серверный движок, а целый сервис вместе с бизнес-логикой? Ну это вряд ли.
Вот об этом я и говорю.
Написать веб-сервер на каком-нибудь 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 персистент слоем.
В настоящем энтерпрайзе сотни сервисов, в каждом по десятку методов, и надо автоматизировать генерацию бойлерплейта.
Почему я не выберу 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)
Что бы такое придумать, чтобы с минимальным синтаксисом добавить логгер?
Можно делать любую кодогенерациюКаким образом? Нужно же не только код нагенерить, но и распарсить уже существующий код (например, узнать типы/наименования параметров функции).
С другой стороны, как это интегрируется в IDE? Можно ли будет дебажить свои функции, которые переколбасил кодогенератор, обернув их логгированием?
В расте более функциональный подход. DI вообще сам по себе довольно плохая штука. В шарпах без него никуда, в других языках можно жить лучше.
Возможно, не хватает хорошей статьи, какие best practices есть rust для enterprise-приложений. В трёхзвенке на java/c# сейчас всё хорошо изучено, как делать сервисы и компоненты, чтобы удобно тестировать и проксировать.
Через год-два можно будет массово кровавый энтерпрайз делать
Это все здорово конечно, только кто будет «массово делать»? И зачем заказчикам связываться с проектами на расте, которые определенно будут дороже?
для строительства сложного дорого комплекса и работе на перспективу он может быть значительно дешевле какого-нибудь C#/Java/Kotlin/Go.
Может быть, а может и не быть, это еще надо проверить, в реальном мире всё может пойти не так, как ожидается. Мне кажется, что вы исходите в основном из объективных возможностей языка, но ведь это же далеко не единственный фактор при выборе технологий — еще есть рынок труда, предпочтения заказчиков и так далее.
Дороже чего? Написать сложную систему на расте может же быть дешевле, по причитам все той же надежностиЯ говорю о таких «enterprise» проектах, где 100500 разных CRUD, перемешанных обильно с «бизнес-логикой». Чтобы выходило дешевле, уровень подготовки программистов нужен невысокий (а у C++ и Rust с этим беда). Лучше, чтобы они не запаривались с «владением», а GC за ними всё убирал.
Так что чем более строгий компилятор, тем меньше багов попадает в прод в принципе. Как раз-таки строгий компилятор не пропускает ошибки джунов в прод, вместо UB/nullref в рантайме на продакшне. Компилятор компенсирует незнание программистом тонких моментов, если перефразировать.
Как альтернатива — IDisposable и using() { }
Для «enterprise»-кода больше и не нужно.
В общем, холиварить из соображений гц вс ручное управление не вижу смысла, borrowing вполне себе хороший способ сделать детерминированное управление памятью без прямого участия разработчика.
Я всё-таки думаю, вопрос в том, что сейчас считается системным программированием, а что прикладным. Не изменились ли со временем критерии?
Браузер, например, всегда считался прикладной программой. Те, кто говорит, что «rust — не для прикладного программирования», что они скажут насчёт такого проекта, как браузер? Lifetime-ы его объектов очень сложные, тут, я думаю, имеет смысл использовать rust.
Это плюс, а не минус. Бросил исключение, а его никто не обрабатывает, упс.Это забота платформы. Никто не обрабатывает — ошибка 500 HTTP-сервера, либо можно написать обработчик, чтобы предоставить вызывающей стороне детали из Exception.Message, залогировать опять же единообразно. У меня, например, нет идей, как в расте получить понятный читаемый лог ошибок единообразным способом, если в типе
result<T,E>
тип E может в принципе быть вообще любым. Да и стектрейс в логе ошибок видеть очень уж удобно.Я не хочу обрабатывать того, чего я не хочу.
Если в язык встроен механизм эксплицитного принуждения делать неприятные вещи и им кто-то пользуется — ты сразу видишь, как это происходит. В Java есть checked exceptions, все просто в IDE генерят пустые обработчики для них, и давай досвиданья.
Если же способа бороться с эксплицитным принуждением нет, то люди просто не пользуются такой технологией и все.
А checked exceptions кстати получили продолжение в виде эффектов, которые не только исключения помечают, а вообще любые побочки. И люди этим даже пользуются.
Чуть ниже: «А что плохого что язык системный?»
И ещё чуть ниже: «Писать веб-сервер на системном расте?!?»
мне кажется это вполне доказательство что метка исключительно «системный» плохо влияет на восприятие языка как прикладного, которым раст вполне является, уж что-что, а веб-сервер на 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
Самые крупные фичи, которые ожидаются до конца года — это упрощение модульной системы, async/await, улучшение футур и специализация (нужна для применения ООП паттернов, и не только). Всё это не особо нужные для «системного программирования», зато нужные для этого вещи (стабилизация lang_items, более полноценная поддержка no_std,… ) на втором плане. Как помне, тренд на веб довольно заметный.
Конечено можно. C++ один из ведущих языков по количеству пользователей и Rust дает для этих программистов надежду на светлое будущее и освобождение от оков legacy. Будь я программистом на C++ я бы тоже любил Rust. Да и вообще для задач где важно четкий контроль за ресурсами и производительностью Rust рулит. Но вот если решаешь чисто бизнес-задачу, то голова занята другим. Не хочется думать о том, надо передавать объект по значению, по ссылке или в коробке, какой у него лайфтайм и т.п. Язык прикладного программирования не должен отвлекать программиста от его бизнес-задачи на эти мелочи.
Воспользуюсь случаем спросить для сбора данных. А какой у вас use-case для рефлексии? Для чего вы её используете (если для разного, то расскажи в порядке важности, пожалуйста)?
Про сериализацию знаем. Делаем так, чтобы можно было сериализовывать из/в любых форматов (в т.ч. в БД) без рефлексии, то есть очень быстро и абсолютно 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 без всякой рефлексии.
Вот здесь не понял. Можете пояснить на примере? Буду очень признателен если дадите ссылку на issue.
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 должна инстанциироваться не так явно, а через тип. Надеюсь так чуть понятнее.
Ну дык и в kotlinx.serialization
можно игнорировать "лишние" поля, и default там замечательно прописываются. В чём проблема?
Насчёт совместимости и raw-типов. Задача: есть старая версия Java-класса, где некий метод возвращает raw List, и новая версия, где List<String>
. Могу ли я сделать Котлин-класс, который вызывает этот метод и способен линковаться с любой из этих двух версий? Скажем, я пишу плагин к системе и хочу уметь работать со старой и новой версией системы. На чистой джаве никаких проблем нет, такое изменение не считается ломающим совместимость на уровне исходного кода.
Сейчас идет работа над книжкой, которая выйдет в этом или следующем году, и она будет специально для новичков. В перспективе, на сайте тоже будет.
Наконец-то! Останется только придумать материалы для опытных программистов, знающих НЕ Джаву. А то пару лет назад пытался понятъ Котлин, но все материалы строились по принципу: "Ну, вы же знаете, как это в Джаве. А у нас почти так же, но вот такие плюшки!"
То есть, чтобы взыться за Котлин, надо было заранее знать Джаву, почти без вариантов
Я уже Джаву выучил почти, а Котлин так и никак. И будьте уверены, в данный момент ориентируясь именно на неё, так как все мои вложения в образование относятся, внезапно, к Джаве.
Что бы еще ну очень хотелось бы от корутин в Котлине:
- вещь подобная ThreadLocal, которая бы идентифицировала текущий scope корутины, например доступ к CoroutineContext
- более-менее стандартизированная сериализация корутин. Для длительных бизнес-процессов или пользовательского ввода требуется заперсистить состояние корутины в базе данных, чтобы освободить память. Хотелось бы чтобы при деплое новой версии корутины можно было бы подправить ручками сериализированные данные от старой корутины.
- улучшить трассировочную информацию при возникновении исключения. То, что лежит в stackTrace совсем не то, чтобы хотелось иметь в итоге.
Ну вроде как уже давно есть top-level val coroutineContext через который можно в любой момент получить доступ к текущему контексту, плюс недавно мы доделали затаскивание любых threadLocal в контекст через ThreadLocal.asContextElement()
Ну вот это вряд ли, ибо сериализация это в принципе сложная доменно-специфичная штука, которой тяжело быть "стандартной". Вот в Java пытались, так теперь жалеют. Но в 1.3 будет намного лучше чем сейчас. С помощью того же Cryo в версии 1.3 будет очень легко записывать и восстанавливать состояние корутины (конечно, если её состояние поддается сериализации через Cryo).
Уже есть прототип. В 1.3 всё будет. Конечно, дизайн JDK exceptions (Throwable) для этого не очень подходит, но нам вроде удалось получить адекватный вариант.
В какой то момент начались таргетинги на разные платформы: 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() }
}
Есть много нюансов с обработкой ошибок при инициализации и освобождении, которые надо учесть, чтобы не допустить утечек.
В ближайшем будущем не стоит ожидать в языке Котлин чего-то масштаба RAII в C++ или Lifetimes в Rust. Котлин в настоящее время ориентируется на прикладных программистов, которым редко приходится работать с внешними ресурсами, требующими явного освобождения/закрытия. Для целевой аудитории языка Котлин конструкций языка типа use и её подобных в целом хватает. В сложных случаях неудобно, но приоритеты — есть более актуальные задачи дизайна языка.
Однако, в Kotlin/Native, который ставит целью удобно интегрироваться с нативной (С) экосистемой, где ручное управление памятью и ресурсами широко распространено, есть дополнительные механизмы в виде арен и memScoped выделение памяти.
Спасибо за ответ.
Вот так и получается, что отсутствие GC принуждает иметь средства для управления любыми ресурсами (память, сокеты, файлы и т.п.), а если он есть, то можно ни о чём не думать до встречи с нативными ресурсами, и тут становится неудобно.
Я согласен, что это редко приходится делать, но иногда, к сожалению, даже в чисто прикладных программах возникает такая необходимость.
Да. Всё так. Некоторые языки пытались совместить полностью автоматический GC и ручное управление ресурсами, но успешных примеров (среди популярных языков) в настоящее время нет. Если кто-то что-нибудь на эту тему придумает, то мы с радостью реализуем.
Ну как мне кажется, Раст это как раз попытка сделать такой язык. Полностью автоматическое управлеение как ресурсами, так и памятью. Но приходится танцевать в случаях циклических зависимостей, т.к. это на порядок усложняет лайтаймы. Так что серебрянной пули действительно пока нет. Так что будем ждать, да.
Такая попытка была в языке D. В Rust же, напротив, сделали всё верно и не пошли по этому пути. В Rust нет никакого "полностью автоматического управления памятью". Программисту на Rust, как и полагается системному программисту, надо самому думать об управлении памятью и правильно применять всякие аннотации времени жизни объектов и т.п. Отличие языка Rust от C и C++ в том, что Rust из-за этого получается более надежным, так как ошибки работы с памятью (если не использовать unsafe) приведут к ошибке компиляции, а не к падению во время исполнения.
надо самому думать об управлении памятью и правильно применять всякие аннотации времени жизни объектов и т.п
Что мне нравится в расте, что можно на нем писать «несистемно», а именно вообще не исползовать лайфтаймы. Всегда можно ссылку заменить на лишнее копирование. Да, неэффективно. Но зато просто и понятно. А за счет общей хорошей производительности языка можно добиться лучшей производительности при той же понятности, как в «несистемных» языках.
В C++ тоже не надо писать delete или close. Rust развивает эту идею, вынося все опасные операции в unsafe. Это круто. Но дело же не в том, что надо писать. Важно то, о чем программисту надо думать. И в Rust и в C++ программисту надо думать о том чем объекты владеют и какой вообще у того или иного объекта lifetime. В этом и основное отличие современных прикладных языков с полностью автоматическим управлением памяти, что программисту об этом думать не надо вообще. Ни декларативно, ни императивно.
В порядке убывания популярности по TIOBE: Java, Python, VB.NET, C#, PHP, JS, Ruby, Go, Perl, Dart, F#, Scala и т.д.
Это уже обсудили в соседней ветке. В прикладных языках программистам редко приходится работать с ресурсами и, в этих редких случаях, приходится страдать. Но как бонус не надо думать о памяти в 99.99% обычного прикладного кода где нет никаких ресурсов. Это, кстати, очень надежный признак который позволяет отличить современный прикладной язык от языка системного программирования.
Да, это уже обсудили в соседней ветке и выяснили, что думать о времени жизни — нужно.
Кстати, сможете ли вы найти ошибку вот в этих строчках (реальный код и довольно популярная ошибка):
using (var reader = XmlReader.Create(bytes))
{
return Message.CreateMessage(bytes, int.MaxValue, MessageVersion);
}
А как у нас с SAST для Kotlin?
«Мы даже не пытаемся запустить старый код, такой задачи у нас не стоит в принципе» — Роман Елизаров о разработке Kotlin