Comments 62
При всем уважении к проделанной работе, это издевательство, а не код. Не было бы у сообщества запроса на новый язык, если что-то уже написанное можно было бы вот так взять и сконвертировать в другой синтаксис.
Сделайте меня развидеть это, пожалуйста.
Автогенерация кода на Расте — прекрасная идея, все= ниуя не понятно =)
Если это коммерческий продукт, то почему он не написан на компилируемом языке и с помощью C API предоставляет интерфейс всем желающим, от PHP до Java?
Да, понимаю, что для какого-нибудь C написать такой конвертер сложнее, чем для Java. Но теперь, если конвертер будет доделан на rust'е, то (наверное) особого смысла поддерживать такое большое количество языков нет, по крайней мере python и C прекрасно умеют в FFI.
Есть ведь Core RT и il2cpp.
Возможно, продукт разрабатывался изначально не с целью предоставлять его в качестве библиотеки всем подряд.
Это не Rust.
И по сложности отладки.
пришлось мне в коде SDK C# заменить все long на int, благо их оказалось немного
То есть я всё понимаю, но вот это… То есть что вы собираетесь делать если вам этот самый long действительно нужен? Что вы будете делать если вам нужен unsigned тип, а Java их не поддерживает?
И самое главное зачем всё это? То есть одноразово для миграции я ещё могу понять. Но зачем такое делать постоянно?
Язык получается не родной. То что вы делаете это perevodite с русского na angliskij kak to taк.
Ну так а зачем вам нужен этот "эквивалентный код на других языках"? Какую задачу вы этим решаете? Какая задача с вашей точки зрения оправдывает такое "кастрирование" С#? Или это просто самоцель?
Обычно подобные задачи решают при помощи выставления надлежащего FFI-интерфейса. И да, компонент, который подлежит встраиванию в проекты на разных языках, всё-таки больше имеет смысл писать на нативном языке.
Вы это сейчас серьёзно? В таком случае надо как минимум тестировать что ваш "эквивалентный код" всё ещё выдаёт "эквивалентный результат".
И я действительно не понимаю в чём проблематика интеграции кода на разных ЯП в одном проекте? Мы это регулярно делаем. И сложности подобной интеграции на мой взгляд даже близко не оправдывают то, как вы "кастрируете" С# чтобы ваш способ работал. Это не С# получается а непонятно что…
Вы смеётесь что-ли? Если у вас какая-то ошибка в вашем "переводчике", то вообще нельзя исключить что "переведённые" автотесты будут проходить на ура, но результат аыполнения функции в той же Java всё равно может отличаться от результата в С#.
Более того я на такое уже натыкался когда у нас джуны "бездумно" портировали код расчёта CRC из С# в Java.
И мне теперь реально интересно что у вас за случай такой, что вам хватает настолько урезанного функционала С#. И нужен ли вам тогда вообще С# как таковой?
Ну вот у вас на странице везде упоминается скорость обработки данных как один из важных критериев.
И даже ни разу не видев вашего кода я готов с вами поспорить что используя нормальный С# вместо вашего урезанного варианта можно будет выжать из системы гораздо больше в этом плане.
Потому что например я уверен что async/await или даже обычное распараллеливание ваш "переводчик" не умеет и поэтому вы их не используете в полном объёме. Если вообще используете…
Полученный в результате трансформации код является кодом на расте лишь формально. Он не предоставляет гарантий, на которые рассчитывают разработчик на расте, и будет падать на всех этих unwrap’ах, вызывая за собой креш всего приложения там, где код на си шарпе всего лишь бросал бы исключение, которое летит до кетч-блока.
То есть, это как если бы конвертер с другого языка в си шарп делал бы Environment.Exit(1); по малейшему поводу вместо бросания исключения.
Сборка под js, java, c#, php, python, lua есть из коробки (не без оговорок конечно, но это было бы несравнимо проще, чем писать свой конвертер с нуля).
Для rust можно было бы наверное собрать шаред библиотеку из с++ таргета (вопрос поиска инструмента для генерации биндингов из с++).
Rust ещё не устоялся, изменения выходят с интервалом 1..2 месяца, (пруфы: май, июнь, июль). С точки зрения практичности, Вы потратили время зря, т.к. обратная совместимость в Расте мало поддерживается.
Из C#-подобных языков я бы упомянул Vala и D lang.
Вам имеет смысл перейти на один из языков, генерирующих нативные бинарники, и гарантирующие C (не C++! ) ABI, и распространять библиотеки в таком виде.
Для Python`истов сгенерить обёртки в CFFI или SWIG, Джавистам завернуть в вызовы JNI, и так далее.
При транспиляции любого сложного кода Вы не сможете гарантировать одинаковое и надёжное поведение Ваших библиотек.
А можете подсказать, что почитать, чтоб легче «врубиться» в обработку естественных языков? Я сейчас игрушечными примерами балуюсь, и хочу плотно заняться NLP.
Для этого вам ещё нужно, по хорошему, продемонстрировать, что семантика теста сохраняется при конвертации, иначе ваши тесты не доказывают вообще ничего.
А где гарантия, что логика сравнения этих сложных структур перенесена правильно?
При этом нет гарантий что ваши автотесты действительно правильно «переводятся» на Java и что «логика сравнения этих сложных структур перенесена правильно».
Ещё раз: если у вас ошибка где-то в «переводчике», то у вас может такое получится что функция будет неправильно «считать байты» и автотест будет неправильно «считать байты». Но при этом результаты функции и автотеста между собой будут совпадать.
Автотест содержит пару {T, R0}, где T — текст, R0 — эталонный результат (сущности с их атрибутами). Пусть R1 = A(T) — результат применения алгоритма A к тексту T. Тест считается удачным, если R0 = R1.
И это по вашему не «тесты типа Assert.IsTrue»?..
Проверка сравнения строк идёт штатными функциями конечного языка, тут ошибки быть не может
Ещё как может. Сравнение строк в различных языках или даже в одном языке при разных «культурах» это та ещё песня…
Если R0 = R1 на всех конечных языках, то конвертер работает неплохо.
«Неплохо» я вижу. Но на мой взгляд такого «неплохо» достаточно мало чтобы довериться одним только автотестам.
Rust ещё не устоялся, изменения выходят с интервалом 1..2 месяца
Вы потратили время зря, т.к. обратная совместимость в Расте мало поддерживается
Релизы компилятора выходят раз в шесть недель. Но какое отношение релизы компилятора имеют к совместимости? С релиза версии 1.0 в 2015 году гарантируется обратная совместимость на уровне исходников.
гарантируется обратная совместимость
Спасибо, не знал.
Но, вообще-то, при выходе новых версий компилятора ошибки / баги возможны.
Сам не пробовал, но вроде бы в проекте можно, по аналогии с версиями библиотек, зафиксировать и версию компилятора/языка.
Да, безусловно, и они случаются. Но, во-первых, они так же быстро исправляются, а во-вторых, каждая фича проходит несколько этапов тестирования, в том числе на реальных проектах. У команды разработчиков языка есть даже возможность пересобрать все открытые библиотеки и приложения и посмотреть, что сломалось.
Как-то ради интереса попробовал взять код lua и чем-то готовым сконвертировать в rust. Получился рабочий, но очень страшный код с unsafe и указателям на каждом шагу. Потом я пришёл к выводу, что в любом случае предстоит огромная работа по переписыванию на идиооматичный раст, и местами возникал соблазн поменять архитектуру. Так как делалось всё из интереса, до конца доводить не стал.
По ощущениям, концепция владения очень сильно влияет на способ написания кода и реально усложняет конвертацию.
Прикольно, что автор пошёл с другой стороны и поменял код до конвертации, а не после.
Базовое отличие Rust от других языков. Это С\С++, Паскаль, Фортран и др. Возникает утечка памяти, если delete не вызвать.
…
В Rust освобождение памяти происходит тоже автоматически, но сразу при окончании жизни объекта, например, когда он выходит из области видимости
Это называется RAII, и в C++ естественно эта фича есть.
Си++, Java, Python и пр., но когда оказывается, что после добавления в список объект нельзя использовать: it = new ...(); list.add(it); it.val = ..., а вот так можно: it = new ...(); it.val = ...; list.add(it);, то это обескураживает
В C++ это тоже есть, move constructor. Использование объекта после std::move
конечно не вызовет ошибку компиляцию, но вот падение программы может.
В общем я бы предложил убрать по крайней мере, C++ из списка "нормальных" языков,
на остальных языках не программировал много поэтому, может и по отношению к ним, в некоторых вопросах, Rust на самом деле не является уникальным.
В C#, Java и др. строки есть последовательность символов char размером 16 бит (в Си 8 бит, в Си++ 8 и 16), что есть ограничение с точки зрения разработчиков Rust. Сейчас Unicode уже 32-битный, а вдруг в будущем он вообще станет 64-битным? А они будут к этому готовы.
И UTF-8, и UTF-16 это кодировки с переменной длинной символа — в обеих кодировках максимальная длина символа 32 бита. Непонятно, где здесь ограничение.
UTF-8 была скорее всего выбрана из-за её большей популярности (даже Windows не так давно стал её поддерживать)
Думаю не столько из-за популярности, сколько из соображения удобства и эффективности: ASCII-текст в UTF-8 кодируется только одним байтом, а не двумя, как в UTF-16.
Системы, перешедшие на юникод раньше других, когда он был 16-битным, переходили на 16-битный вариант, чтобы сохранить сишную строку, как последовательность char'ов, увеличив ее размер «всего» в два раза. Windows NT была среди этих операционок и в то время это была революция, в положительном смысле этого слова.
Однако опыт этих систем выявил много проблем 16-битного представления: только LE/BE чего стоит. А потом и юникод стал 32-битным, и все последующие операционные системы и языки выбирали UTF-8, как единственную систему, одинаковую в любой архитектуре, не содержащую непредставимых в ASCII байтов и оптимизирующую память. Про это даже Джоел писал…
Или чтобы реализовать перекрёстные ссылки между объектами класса Foo, нужно использовать конструкцию Option<Rc<RefCell<Foo>>>, а для доступа к полю val этого класса вызывать foo.unwrap().borrow().val.
Простое правило написания кода на Rust: если вы используете RefCell, скорее всего вы делаете что-то не так. RefCell выносит проверки заимствования в runtime, что опять же негативно влияет на оптимизации.
Выскажу своё личное мнение: мне представляется, что прогресс в области программирования идёт в направлении оптимизации труда программиста, чтобы меньшими усилиями достигать большего эффекта. Случай Rust уникален тем, что не вписывается в эту тенденцию. Удивительным для меня фактом является рост его популярности (сейчас Rust вошёл в 20-ку). Но почему? Вот вопрос.
Случай Rust в некоторых случаях полагает написание дополнительного кода для проверки его компилятором с целью обеспечения безопасности программы (те же lifetimes), в некоторых случаях наоборот к уменьшению кода (например тип-суммы отлично справляются с заменой паттерна visitor в ООП, уменьшая количество кода в разы). Ваша проблема в том, что вы писали неидиоматичный код. Хотя это скорее проблема проекта, так как транслировать идиоматичный C# в идиоматичный Rust кажется мне невозможным.
По производительности на моей задаче Rust не произвёл впечатления — выигрыш по сравнению с C# получился всего в 2 раза.
Удивлен что с тем кодом который вы показали ниже, у вас вообще получилась прибавка к производительности.
В Rust освобождение памяти происходит тоже автоматически, но сразу при окончании жизни объекта, например, когда он выходит из области видимости. Скажем, если внутри блока создать объект {… let x = Foo {… это конструктор};… }, то память автоматически освободится при выходе управления из этого блока.
Уточню: в данном случае память выделяется на стеке, а не на куче. Для выделения на куче следует использовать сырые или умные указатели, вроде Box, Rc, Arc.
Решение — реализовать класс (struct в терминологии Rust), содержащий как вектор символов, так и сам string.
Решение крайне плохое. Вы по сути аллоцируете два раза все ваши NString, так как Clone::clone копирует все данные. Клонирование в таких случаях будет больно бить по производительности, и может считаться неидиоматичным применением. Возможно, следовало бы создать свой тип строки, либо же поискать готовые решения на crates.io.
Например, вот метод Substring(int start, int len) для получения подстроки:
Вопрос, нужно ли вам в данном примере владение NString на выходе. Я думаю, что скорее всего не нужно было, достаточно было возвращать &str.
Причём получилось 3 обёртки для каждого типа в зависимости от типа элементов: для простых типов, для ссылок &T и для владений T.
Скорее всего достаточно один раз было написать коллекцию, с использование generic-параметра. Какой-то go-style у вас вышел.
В Rust нет привычных всем null и базового класса object, практически отсутствует и приведение типов. То, что в C# любой тип можно «упаковать» в object и затем «распаковать» его — для Rust это за пределами возможного.
На этом моменте Вас должно было озарить, что писать конвертер в Rust из c# — плохая идея. То что вы нагородили ниже крайне плохо оптимизируется компилятором, и выносит кучу ненужных проверок в runtime.
Обратим внимание: для шарпового obj = cnt.First на Rust получается obj = ObjValue::from_item(Some(Rc::clone(cnt.borrow().get_first().as_ref().unwrap()))). Что говорите, это жесть? Нет, это Раст!
Говорите это Rust? Нет, это попытка натянуть сову на глобус. В обычном Rust-коде за такое оторвали бы руки.
Аналогом класса C# в Rust выступает struct
Не совсем корректно. В Rust нет понятия класса. Аналогом struct из Rust'a в C# скорее всего выступит тот же самый struct, с некоторыми различиями.
Ваш IItem с динамическим полиморфизмом это также крайне неидиоматичный код. Мало того, что динамический полиморфизм следует использовать только в случаях с крайней необходимостью, когда ничего другого уже не работает, так в данном случае достаточно было бы использовать generic-параметр, что делается в коллекциях и в самом C#.
Короче, когда таких ссылок становится много, наступает lifetime-hell, как я его назвал. Да, Rust тоже внёс свой вклад в коллекцию этих хеллов!
Это если бездумно вставлять lifetimes, не собо понимая зачем они нужны.
В целом по статье сложилось впечатление что автор незнаком не только с Rust, но и с C#. Как я знаю, использование object в C# тоже не считается хорошим тоном. Также на лицо неумение автора применять generic-параметры, которые также присутсвуют и в C#.
Вопрос к автору, а вы Rustbook открывали хотя бы?..
Да и любой переводчик с естественного языка на язык представляется таким «Франкенштейном», особенно на ранних этапах своего развития. Понятно, что носитель обоих языков переведёт лучше. Но это дорого, и где их взять то, на всех?
Опыт конвертирования кода C# в код Rust