Rust и C# слишком разные языки, чтобы написать сколь угодно рабочий конвертер между ними. То что считается идиоматичным в C#, считается антипаттерном в Rust. Динамический полиморфизм в расте хоть и есть, но он убивает производительность, так как компилятор не может zero-cost оптимизировать его.
Или чтобы реализовать перекрёстные ссылки между объектами класса 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 открывали хотя бы?..
Простое правило написания кода на Rust: если вы используете RefCell, скорее всего вы делаете что-то не так. RefCell выносит проверки заимствования в runtime, что опять же негативно влияет на оптимизации.
Случай Rust в некоторых случаях полагает написание дополнительного кода для проверки его компилятором с целью обеспечения безопасности программы (те же lifetimes), в некоторых случаях наоборот к уменьшению кода (например тип-суммы отлично справляются с заменой паттерна visitor в ООП, уменьшая количество кода в разы). Ваша проблема в том, что вы писали неидиоматичный код. Хотя это скорее проблема проекта, так как транслировать идиоматичный C# в идиоматичный Rust кажется мне невозможным.
Удивлен что с тем кодом который вы показали ниже, у вас вообще получилась прибавка к производительности.
Уточню: в данном случае память выделяется на стеке, а не на куче. Для выделения на куче следует использовать сырые или умные указатели, вроде Box, Rc, Arc.
Решение крайне плохое. Вы по сути аллоцируете два раза все ваши NString, так как Clone::clone копирует все данные. Клонирование в таких случаях будет больно бить по производительности, и может считаться неидиоматичным применением. Возможно, следовало бы создать свой тип строки, либо же поискать готовые решения на crates.io.
Вопрос, нужно ли вам в данном примере владение NString на выходе. Я думаю, что скорее всего не нужно было, достаточно было возвращать &str.
Скорее всего достаточно один раз было написать коллекцию, с использование generic-параметра. Какой-то go-style у вас вышел.
На этом моменте Вас должно было озарить, что писать конвертер в Rust из c# — плохая идея. То что вы нагородили ниже крайне плохо оптимизируется компилятором, и выносит кучу ненужных проверок в runtime.
Говорите это Rust? Нет, это попытка натянуть сову на глобус. В обычном Rust-коде за такое оторвали бы руки.
Не совсем корректно. В Rust нет понятия класса. Аналогом struct из Rust'a в C# скорее всего выступит тот же самый struct, с некоторыми различиями.
Ваш IItem с динамическим полиморфизмом это также крайне неидиоматичный код. Мало того, что динамический полиморфизм следует использовать только в случаях с крайней необходимостью, когда ничего другого уже не работает, так в данном случае достаточно было бы использовать generic-параметр, что делается в коллекциях и в самом C#.
Это если бездумно вставлять lifetimes, не собо понимая зачем они нужны.
В целом по статье сложилось впечатление что автор незнаком не только с Rust, но и с C#. Как я знаю, использование object в C# тоже не считается хорошим тоном. Также на лицо неумение автора применять generic-параметры, которые также присутсвуют и в C#.
Вопрос к автору, а вы Rustbook открывали хотя бы?..