Pull to refresh

Comments 121

Круто! Спасибо за статью. Тоже присматриваюсь к Rust.
На каких языках писали до того как начали использовать Rust. Сложен ли переход был? Что было сложнее всего или просто не привычнее?
Могу поделиться собственным опытом. Делал переход с шарпа на раст. Уже больше полугода пишу в основном только на расте. Первый раз в 16 году попробовал данный язык. Единственная сложность была с лайфтаймами. И судя по общению в чатах у всех новичков проблемы с этим, так как данная особенность существует пока только у раста и не сразу въезжаешь что к чему. Но к счастью явно прописывать время в программе на практике практически не приходится(во всяком случае в прикладных задачах точно можно без этого обходится).

Так же хочется отметить что на расте архитектура кода получается менее перегруженной, чем в той же java или c#. Но к этому нужно придти, потому что в начале я по привычке все равно городил сложные абстракции кода. Но со временем приходит понимание как нужно писать на расте. В итоге решения на расте как правило получаются более читаемые и более тестированные. Я не говорю что данные вещи возможны только на расте, просто на других языках как правило больше возможностей в плане решения задачи. В итоге прописываешь первое попавшиеся и идешь дальше. Хотя данное решение может быть не самым оптимальным. А в расте приходится в некоторых моментах задумываться.Хочешь ли ты кучу лайфтаймов прописать или можно решить как то задачу более изящным способом. Но как я выше сказал это мой опыт, и может я только писал столько не оптимальный код в других язык))) И по этому возможно для Вас разницы ни какой не будет, после перехода на раст(Ну кроме лайфтаймов)
Я стал использовать Rust больше года назад. До этого писал в основном на функциональных языках. Пока что он прижился у меня, как средство оптимизации Erlang/Elixir приложений и инструмент реализации сложных алгоритмов. Язык собрал в себе множество концепций, и если вы с ними сталкивались в других языках, думаю начать писать не составит особого труда. Многие коллеги жалуются на синтаксис Rust, но у меня такой проблемы не было, впрочем, с Erlang тоже проблем с синтаксисом языка не испытывал.
Резюмируя, лично меня Rust привлекает целостной экосистемой языка, приемлемым уровнем производительности, удобством интеграции в уже существующие проекты. На нем действительно можно писать как большие системы, так и маленькие низкоуровневые блоки.

Переходил на Rust с Java, полтора года назад. Понадобилось использовать C++, но после Java управление внешними зависимостями в C++ показалось настоящим кошмаром. А так, в разное время приходилось программировать на C/C++, Python, PHP, JavaScript и Java.


Переход был достаточно сложен: основная трудность состояла в том, чтобы переучиться и привыкнуть следовать правилам заимствования и владения. Тяжело было чисто психологически, а не технически (ошибки раздражали), ибо компилятор всегда выдавал исчерпывающие сообщения об ошибках и предложения по исправлению. Период "борьбы с компилятором" продолжался несколько месяцев (!), все время, пока я осваивал язык. Но после этого языком стало пользоваться весьма комфортно.


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


В целом — я доволен Rust'ом и пытаюсь его больше использовать в своих проектах, причем в основном для прикладных целей. К сожалению, не всегда это возможно: из-за молодости языка заказчики бывают против его использования. Еще минус — некоторая сырость языка и экосистемы вокруг него все еще присутствует. С каждым новым релизом (раз в 6 недель) она постепенно преодолевается, и сейчас состояние намного лучше, чем было год назад.

Хотел уточнить, а при переходе на Rust, у вас не возникло проблем с тем, что на нём немного сложно писать программы в объектно-ориентированном стиле? Ну, по крайней мере для меня. Вот что меня немного смутило:
1) Невозможно наследовать структуры данных. Да, я знаю про агрегацию и композицию, но это не всегда применимо. Возникала ли у вас подобная проблема, и если да, то как вы с ней справлялись?
2) При наследовании трейтов не сохраняются реализации функций. Т.е. я, скажем наследую В от А, в А есть реализация какой-то функции, но вот в В её уже не будет. Я читал много SO и доков и там есть решения, но это всё же немного не то. Были ли у вас проблемы с этим?
3) Более-менее сложная иерархия объектов выглядит довольно громоздко. Допустим, какой-нибудь простой оконный интерфейс, где есть отношение parent-child между виджетами, причём, дочерний виджет, допустим может хранить слабую ссылку на родительский, в некоторых случаях. Да, это возможно и реализуемо, но выглядит переусложнённым.

Я тоже пробовал писать на Rust, но столкнулся с проблемами, которые я описал выше, и для себя решил, что я это язык сугубо для системного программирования, и альтернатива скорее С, чем С++. Хотя вот вижу, что ребята переходят с C# и Java, в общем-то вполне себе ОО-языков для прикладного программирования, и меня начинают мучить сомнения… Мб я сильно где-то не прав?

1) Где вам нужно наследовать данные — агрегация всегда применима, разве нет? Другое дело, что если таким образом имитировать наследование в стиле ООП, то придется писать много дополнительного кода с пробросом вызова методов. Но так писать не надо ) Я стараюсь просто избегать раздувания структур и создания больших иерархий. Лучше создавать больше маленьких типов, активнее пользоваться типажами и средствами обобщенного программирования. Проблема с этим была только в самом начале, когда я пытался писать на Rust в стиле ООП.


2) Не уверен, что правильно понял ваш вопрос, но предположу, что после:


trait Foo {
    fn foo(&self);
}

trait FooBar: Foo {
    fn foobar(&self);
}

Вы ожидаете, что в FooBar появится метод foo. Это не так, потому что требование реализации : Foo относится к тому типу, который будет реализовывать FooBar, а не к самому типажу FooBar. Строго говоря, вы не можете наследовать методы между типажами. Да и зачем это нужно? Необходимости в этом у меня не возникало.


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


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

1) Нет не всегда. Приведу один пример. Допустим я получаю какой-то JSON по сети и мне его надо смапить в структуру данных. Всё прекрасно, я использую serde и радуюсь жизни. Но дальше мне нужно получить другой JSON по сети, который отличается на одно-два поля и точно так же его смапить в структуру, которая просто представляет собой расширение первой. Вот тут агрегация просто не слишком удобна, хотя и возможна, но не так удобная.
2) Допустим я хочу использовать интерфейс IFoo, и его реализацию по умолчанию IFooBase. Ну и наследоваться от IFooBase, и только там, где мне надо реализовывать другое поведение, но не во всех классах.
3) Мне да, очень часто.

С обобщённым программированием я знаком (на плюсах в основном), что такое характеристики типов я понимаю. В плане наследования я с вами не совсем согласен, т.к. считаю, что образование подтипа, путём наследования данных — это вполне себе валидно. Популярные и хорошо написанные C++ библиотеки и использую и код смотрю. Думаю, что надо больше посмотреть для Rust.

Сдаётся мне, что пока я просто или не до конца понимаю как использовать Rust для прикладного программирования, или просто неправильно его использую.
1) Часто в таких случаях хватает одной структуры в которой «расширенные» поля будут «опциональными».
2) Чем вас не устраивают методы трейтов с реализацией «по умолчанию»? Не нужно никакого FooBase, вы просто пишете:
trait Foo {
    // обязательный к реализации метод
    fn foo1(&self);
    // метод с дефолтной реализацией на основе метода foo1
    fn foo2(&self) { .. }
}

// типаж-расширение (extension trait) для которого все методы реализованы
// с использованием методов типажа Foo
trait Bar: Foo {
    fn bar1(&self) { .. }
    fn bar2(&self) { .. }
}

struct A;

// поведение foo2 можно изменить
impl Foo for A {
    fn foo1(&self) { .. }
}

// здесь можно переписать поведение методов типажа Bar при необходимости
// стоит отметить что без этой строчки вы не сможете использовать методы
// типажа Bar
impl Bar for A {}


3) Это означает что ООП подход сильно повлиял на ваш стиль мышления (импринтинг), и что, как уже написали, вам нужно ломать свои привычки и учиться смотреть на задачи с другой стороны. Дело нелёгкое и для многих весьма неприятное, но в целом полезное.
1) В том-то и дело, что модель уже не получается такой чистой, если в ней будут какий-то опциональные поля. А если мне скажем нужно размапить 5-10 разных структур, которые представляют собой различные комбинации каких двух-трёх базовых? Предлагаете вводить одну большую структуру на все случаи жизни?
2) Спасибо посмотрю, действительно ли это то, что я хочу.
3) Да, согласен, не помешает.
  1. А что, если структуры у вас разойдутся по полям? Почти всегда лучше выделить другую структуру обычным копи-пастом. Потому как сегодня общих полей — 2, а завтра — 3. Если просто так совпали звезды и структуры похожи, то это не повод переиспользовать их структуру, это можно быть дорогой в никуда (примерно, как наследовать Point3D : Point2D { public int Z }). В крайнем случае можно просто написать макрос в стиле "скопируй поля вот той структуры". Мерзковато, но возможно.
  2. Первый трейт называется Bar, опечатка.
  3. Ответил ниже.
UFO just landed and posted this here

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


Обычно используется простое решение с Option, но оно не даст статических гарантий отсутствия значения:


#[derive(PartialEq, PartialOrd, Debug)]
struct Struct {
    name: String,
    age: i32,
    salary: i32,
    dept: Option<String>,
    smth_else: Option<i32>,
}

let reduced = Struct { name: "meh".to_string(), age: 20, salary: 42, dept: None, smth_else: None };
let full = Struct { name: "meh".to_string(), age: 20, salary: 42, dept: Some("IT".to_string()), smth_else: Some(10) };

Здесь reduced и full будут одного и того же типа, со всеми вытекающими.

Как выше сказано, у вас чувствуется очень сильное влияние ООП на стиль мышления.

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

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

В общем, с точки зрения образования оно того стоит. ООП это не панацея, просто набор практик, хороший, но не единственный. Тот же Expression Problem очень неприятен при расширении иерарахии. В итоге в ООП мире обычно все сводится к заучиванию паттерна визитор и все. А в языках с богатыми типами возможно вообще оптимальное решение задачи с помощью tagless final. А вот в ООП оно реализуется, увы, костыльно, хотя и не нереально. Тут подробнее.
UFO just landed and posted this here

Как ни странно, не только пройдёт проверку, но даже и не потребует явным образом импортировать Foo: следующий код выводит HERE.


mod foo {
    pub trait Foo {
        fn foo(&self);
    }
}

mod foobar {
    use crate::foo::Foo;
    pub trait FooBar : Foo {
        fn foobar(&self) {
            self.foo();
        }
    }
}

mod strct {
    use crate::foo::Foo;
    use crate::foobar::FooBar;

    pub struct Struct {}

    impl Foo for Struct {
        fn foo(&self) {
            println!("HERE");
        }
    }

    impl FooBar for Struct {}
}

mod runfoo {
    use crate::foobar::FooBar;

    pub fn run_foo(s : &FooBar) {
        s.foo();
    }
}

fn main() {
    let s = strct::Struct {};
    runfoo::run_foo(&s);
}

Вот если у вас много foo, то извольте и импортировать, и указать явно, какой вы хотите (playground):


mod foo {
    pub trait Foo {
        fn foo(&self);
    }
}

mod foo2 {
    pub trait Foo {
        fn foo(&self);
    }
}

mod foobar {
    use crate::foo::Foo;
    use crate::foo2::Foo as Foo2;

    pub trait FooBar : Foo {
        fn foobar(&self) {
            self.foo();
        }
    }

    pub trait FooAllBar : Foo + Foo2 {
        fn foobar(&self) {
            Foo2::foo(self);
        }
    }
}

mod strct {
    use crate::foo::Foo;
    use crate::foo2::Foo as Foo2;
    use crate::foobar::FooBar;
    use crate::foobar::FooAllBar;

    pub struct Struct {}

    impl Foo for Struct {
        fn foo(&self) {
            println!("HERE");
        }
    }

    impl Foo2 for Struct {
        fn foo(&self) {
            println!("THERE");
        }
    }

    impl FooBar for Struct {}
    impl FooAllBar for Struct {}
}

mod runfoo {
    use crate::foobar::FooBar;

    pub fn run_foo(s : &FooBar) {
        s.foo();
    }
}

mod runfooall {
    use crate::foobar::FooAllBar;
    use crate::foo2::Foo;

    pub fn run_foo(s : &FooAllBar) {
        Foo::foo(s);
    }
}

fn main() {
    let s = strct::Struct {};
    runfoo::run_foo(&s);
    runfooall::run_foo(&s);
}

Тут есть небольшая проблема, если я правильно понимаю, &FooBar привести к &Foo не получится.


Например, если run_foo вдруг захочет вызвать другую функцию, которой нужен &Foo. Или вернуть &Foo в составе какой-то структуры.


В первом случае ещё можно выкрутиться как-то так (ну или через newtype):


impl <'a> Foo for &'a FooBar {
    fn foo(&self) {
        FooBar::foo(*self)
    }
}

А вот вернуть из функции &Foo, если дан &FooBar уже никак (впрочем, решается тривиально через добавление функции fn as_foo(&self) -> &Foo на FooBar).

просто сделайте fn foo<T: Foo>(foo: &T) -> &T { foo }, и не затирайте тип. Хотя зачем возвращать самого себя в данном контексте не совсем понятно.

Да, но бывает, что динамическая диспетчеризация предпочтительнее. Особенно в крупном проекте.

Понятно, что пример совершенно искусственный.

После ООП языков с Rust достаточно тяжело. Привычные приёмы не работают, попытки писать в привычном стиле ведут к достаточно кривому и некрасивому коду. С Rust нужно переучиваться. Но, когда понимаешь, как здесь правильно писать, всё становится на порядок проще, код становится лаконичен, прекращается борьба с компилятором и вместо соперника он становится другом и на самом деле очень мощным инструментом, берущим на себя массу рутинной работы.


При наследовании трейтов не сохраняются реализации функций

На самом деле это не совсем так. Реализации, сделанные в трейтах — вполне наследуются, но это не то наследование к которому Вы могли привыкнуть в других языках.


https://play.rust-lang.org/?version=stable&mode=debug&edition=2015&gist=f187e1d7dc3c6e9290c63558c25bd7d3


Запись B: A, выглядит несомненно как наследование, но, несмотря на сходство — это не оно. Это декларация ограничения — типаж B может быть реализован только для тех типов, для которых реализован типаж A. Соответственно типаж B может расчитывать на методы типажа A, но он не находится в привилегированном положении наследника (и таких типажей может быть реализовано несколько для одного типа), он не переопределяет реализации из другого типажа, а при вызове без уточнения, какую реализацию мы желаем вызвать, компилятор закономерно удивится. Привычное интуитивное "наследование" здесь не работает, хотя вначале кажется — вот оно, и пытаешься строить наследование на типажах :-)


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

Спасибо, это хороший пример!

Будет здорово, если вы напишите статью о переходе с ОО язков на Rust. Думаю, многие скажут вам спасибо, да и карму вам выше 4х поднять получится, а то сейчас хабр не даёт :)

Я планировал написать статью еще полгода назад, но все руки не доходят, про то, как выглядит написание телеграм-бота глазами C# разработчика (меня). Только я сейчас смотрю на все эти футуры в 3 экрана, и думаю, что лучше подождать релиза async/await, а то вместо привлечения людей только напугаю всех :)

Тоже было бы интересно посмотреть.
Однако я в большей степени говорил о статье, построенной по принципу, который использовал Майерс в молодости, когда переучивал С разработчиков на C++: «ты делал так, а вот теперь надо вот так вот». Ну, а глубина пояснения почему надо так может быть в принципе любой.
Ну, можно и без асинхрона остальное написать.
У меня четкое неприятие синхронного ожидания асинхронного кода. Тем более, что в расте «забить» и «сделать хоть как-то» зачастую так же сложно или сложнее, чем сделать корректно.
Тоже был бы рад статье как с ООП C++/C# перейти на Rust.
И хотелось бы в этом году, а не через пять лет.
Пожалуй, начну понемногу черновик пописывать. А то то, что я помнил полгода назад, сейчас уже не вспомню, а информация была важная… Спасибо за мотивацию.

Я немного писал на С++ и Java, пробовал много других языков, но в конце концов (это произошло в начале 2017 года) начал писать на расте и с тех пор пишу только на нем. Не могу сказать, что с самим языком возникали какие-то особенные трудности — первая версия официальной книги позволяла начать писать простые программы и читать чужой код буквально через пару недель чтения по вечерам. Сейчас уже есть уже третье, если не ошибаюсь, издание этой книги — не читал новые версии, но беглое просматривание показывает, что все стало только лучше.
Все новички непременно сталкиваются с лайфтаймами — да, у меня тоже было время, когда ты рандомно расставляешь в коде неведомые закорючки и молишься, чтобы компилятор сжалился. Впрочем, это довольно быстро проходит после некоторой практики. В ежедневной разработке с лайфтаймами почти не сталкиваешься — в большинстве случаев они просто не нужны или IDE расставит их за тебя.


Читается чужой код на расте очень легко — этому способствует удобная система документации, строгий синтаксис с возможностью автоматического форматирования и статическая типизация. Советую поискать на гитхабе проекты из той сферы, которая вас больше всего привлекает — и попытаться решить какую-нибудь из issues. Во многих проектах даже ведутся специальные списки задач для новичков.


Русскоязычное сообщество (по крайней мере та часть, что находится за пределами Телеграма) — очень приветливое, тоже самое можно сказать и про международное сообщество. Ссылок к сожалению не дам — модераторы свирепствуют :)


P.S. freecoder_xx отличная статья, мне прям понравилось. Долго писал?

80% статьи я написал за один вечер. А оставшиеся 20% растянулись на два дня :)
До Rust был обширный опыт c Java, а также Go. Ну и C/C++ тоже, но всё чаще в режиме read-only. Начал писать на Rust совершенно безболезненно, я бы даже сказал — в эйфории, настолько там всё круто и продумано. Эйфория, конечно, прошла, но язык этот всё равно отличный. Когда сравниваешь его с любым другим языком (вообще любым из известных мне) — то на ум почему-то приходит слово «беспомощный» применительно к этому второму языку.

Главный минус у него ровно один — он сейчас мало где используется в продакшене, и у работодателей не слишком-то популярен (как говорит один популярный сайт, «There are no open rust jobs anywhere in the world. That doesn't sounds right...»), поэтому пока все мои проекты на Rust — личные.
Не против продолжить в личке общение на эту тему, если вам интересно (Мск либо удалёнка).
можете мне написать на hire@flussonic.com?

UFO just landed and posted this here

Писал на Rust несколько проектов:


  • Самый первый проект на Rust: преобразователь UART в CAN на основе микроконтроллера STM32. До этого такие проекты у нас писались на C, хотя я лично их не писал.


    Из плюсов:


    • Уже были отличные библиотеки (cortex-m*) и несколько структур данных для асинхронного программирования (вида «получили в прерывании байты, записали в очередь, в основном цикле обработали, записали ответ в очередь на отправку, начали отправку, уснули»). Найти готовую реализацию очереди для микроконтроллеров на C можно. Подключить её нормально в качестве зависимости — нет.
    • Написать тесты, запускающиеся на хосте, с Rust легче.
    • Отладка с gdb уже работает.

    Из минусов:


    • Экосистема только развивающаяся. Многое из готового не найти. Значительная часть проектов висели на одном гуру (кстати, часть из них смотрю перехали в аккаунт организации https://github.com/rust-embedded). Там, где я реализовывал собственно UART и CAN было несколько больше unsafe кода, чем нужно, прямые записи в регистры. Пытаться создавать безопасные абстракции, соответствующие духу языка, я не стал — мало времени и, к тому же, я отлично понимаю, что в первом проекте «как надо» не получится ни за что.
    • Непривычные макросы. Я не могу просто взять и «как в C» обратиться к переменной, объявленной за пределами макроса, без передачи её каким‐либо явным образом. Здесь, с одной стороны, всегда видно, что код макроса зависит от переменной. С другой стороны, вызовы макросов становятся длиннее.
    • Для того, чтобы установить бит в РСН на Rust нужно написать конструкцию вида apb1.enr().modify(|_, w| w.i2c1().set_bit());. Как видите, здесь пять(!) вызовов функций (.enr(), .modify(), вызов лямбды (внутри .modify()), .i2c1(), .set_bit()). Компилятор это каким‐то образом оптимизирует во что‐то адекватное, но, во‐первых, выглядит для человека, писавшего на C, это дико. Во‐вторых, если вы попросите его не оптимизировать, то он не оптимизирует — при отладке такое сильно замедляет программу.

  • Писал программу, проводящую функциональный контроль микросхемы. Раньше писал такое на LabVIEW. Точнее не всю программу, а только часть, собственно анализирующую результаты работы микросхемы.


    Из плюсов:


    • Rust может компилироваться в DLL, которую можно вызвать из LabVIEW.
    • На Rust легко написать простой tcp сервер.
    • На Rust можно быстрее писать сложные программы, чем на LabVIEW (кто бы сомневался).
    • Опять же, тесты создать проще.
    • Легко обойтись без аллокаций памяти в куче.
    • Rust обрабатывает данные быстрее LabVIEW.

    Из минусов:


    • Rust при использовании через DLL роняет LabVIEW. И вообще, этот интерфейс в LabVIEW медленнее, чем я думал (но это, конечно, не проблема Rust). Падения я сначала записывал на паники вроде тех, что происходят при переполнении в арифметических операциях, но исправление замеченных ошибок и изменение операторов сложения/вычитания/умножения на подходящие по смыслу функции вроде saturating_add/wrapping_add/… ничего не дали. После переписывание DLL в tcp сервер LabVIEW перестал падать, а сервер падать не стал.
    • Паники сложно перехватывать. Насколько я понял, перехват не всегда работает. Мой код перехвата, кажется, так ни разу и не заработал — хотя, возможно, паник просто не было, а падения вызывались чем‐то ещё.
    • Немного повоевал с borrow checker когда решил вести в программе отдельный журнал, но при этом получать имя журнала из LabVIEW. А до получения имени — отправлять данные в stderr.

  • Писал программу анализа бинарных логов, созданных LabVIEW. До этого использовалась программа на Python.


    Из плюсов:


    • С поправкой на чтение документации (только третий проект, я ещё не всё запомнил!) пишется примерно так же быстро, как и на Python. В т.ч. можно так же легко создать цепочку вида файл→буфер (правда на Python буфер включён по‐умолчанию и должен явно отключаться, а тут он является отдельной сущностью и должен быть прописан явно)→потоковый распаковщик (xz или gz, по расширению) и передать её в качестве объекта std::io::Read в процедуры анализа.
    • При этом работает на два порядка быстрее, при том что ни оптимизацией программы на Python, ни оптимизацией программы на Rust я не занимался.
    • Компилятор вылавливал те ошибки, которые на Python я бы не заметил до запуска анализа журнала с определёнными данными.

    Из минусов:


    • Вот здесь уже пришлось много воевать с компилятором за то, чтобы вернуть trait object (тот самый std::io::Read) из функции и потом передать в другую функцию. Основная проблема: не сразу понял, что нужно делать, когда компилятор мне говорит, что он не знает размер данных и по этому поводу не хочет ничего никуда передавать.
    • Лапша парсера и сериализатора результатов в человекочитаемую форму на Python всё же короче.

Писал в основном на Java более 10 лет (другие языки использовал наскоками, по мере необходимости).

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

С владением в Rust поначалу было непривычно, но процентов 80, наверное, освоил довольно быстро (простые случаи). Более глубокое понимание на уровне интуиции, наверное, выработалось где-то через полгода, особенно после разработки нескольких вариантов API типа reflection в Java (обобщенный обход данных заданных некоторой схемой).

Из необычного было то, что мы используем Rust совсем не как системный язык программирования, а для самого что ни на есть энтерпрайза, фактически как аналог Java / .NET. Поэтому, сложно сказать, какая часть трудностей из-за языка как такового, а какая — из-за особенностей его применения. Часть сложностей была, возможно, из-за недостатка опыта (у меня) в обоих областях: и в знании Rust, и в знании предметной области.

Приходилось проявлять, гм, смекалку. Даже, не побоюсь признаться, приходится заимствовать некоторые подходы из Java (что порой вызывает удивление коллег).

С точки зрения «разгона» команды, старт у нас был довольно трудным, на мой взгляд, — Rust довольно сложный язык, особенно как второй язык после JavaScript. Но постепенно ситуация исправляется, сейчас у нас примерно человек 8 пишущих активно на Rust. Плюс нам повезло нанять пару человек, имеющих активное участи в Rust экосистеме (в том числе и с позиции менторинга), поэтому я настроен оптимистично :)

У меня нет опыта разработки и поддержки больших систем на динамически типизированных языках (Java отнесём в категорию «условно статически-типизированных»), но пока что наш код на Rust переживает рефакторинги весьма успешно.

UFO just landed and posted this here
Без завершающих символов код конечно выглядит лаконичнее, но это же ограничивает форматирование. Приходится «сражаться» с парсером, чтобы тот понял где должны заканчиваться выражения и чтобы код выглядел и хорошо.
ООП как такового в Rust нет (об этом упоминается в статье). Rust использует параметрический полиморфизм, а вместо наследования данных предлагается использовать аггрегацию. В таком случае смешивать наследование и полиморфизм подтипов, как зачастую происходит в ООП, не получится.

Добавлю к вышесказанному, что с одной стороны, элементы ООП всё же есть — со структурами можно работать как с объектами, типажи можно наследовать и даже есть типаж-объекты, но с другой стороны, отсутствие классов и наследования структур данных — это фича языка, введённая с целью избежать негативных практик ООП, в том числе чрезмерно разрастающихся иерархий классов, которые становится впоследствии дорого поддерживать и развивать. И на примере достаточно крупных проектов можно сказать, что это достаточно продуктивная модель.
Вообще, Rust — это очередной гибрид функционального и объектного подходов, с существенным перевесом в пользу функционального, но без возведения его в абсолют. Однако история о том, какой будет ООП сторона Rust, ещё не завершена, и соответствующие фичи и RFC потихоньку пилятся и эволюционируют.

на примере достаточно крупных проектов

А можно примеры? Прям чтоб чтот крупное. А то с ходу нагуглися только проект Мозиллы Servo, но это и понятно.
Parity, actix, chalk — первое, что в голову пришло.
UFO just landed and posted this here
А есть ли уже сборник шаблонов проектирования или best practices?
Я периодически мониторю реддит — вопросы о таковых всплывают регулярно, ответы — в стиле ниже, «откройте крупный проект и смотрите».
Из тех, что я видел — пребывают в заброшенном состоянии. Сейчас, видимо, период практических экспериментов, теоретические осмысления появятся позже. Поэтому приходится высматривать паттерны в исходных кодах популярных библиотек.
Наверное это одна из основных причин, по которым до сих пор нет вменяемой UI библиотеки. Там надо четко просматриваемые шаблоны расширения/переиспользования/переопределения функционала.

Ну, паттернов там по факту нет вовсе. И это немного не best practices, больше как туториал по языку на уровне чуть выше базового. Эти ссылки знаю. Awesome когда-то давно мониторил, потом забросил, спасибо за напоминание.

Вот бы еще сказали, на сколько весь этот синтаксический и языковой сахар влияет на производительность результирующего кода?

Ведь речь идет о языке системного уровня, я понимаю, на уровне языка уменьшать количество ошибок это уже круто, но производительность было до сих пор чуть ли не главным достоинством c/c++, чье место по уму собирается забирать Rust?

Одна из главных идей раста — так называемые zero-cost abstractions. Это значит, что почти каждая языковая абстракция никак не влияет на производительность. Результаты бенчмарков с другими языками найти достаточно легко — Раст настолько же быстр, насколько и С/C++

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

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

Я пока в Расте совсем новичок, но вот нашёл тут на реддите очень интересный комментарий от человека, как и я, работающего в HFT. А когда речь заходит о скорости — то как раз HFT'шники первыми проявляют интерес.

Так вот, там первым пунктом «no variadic templates». Можно ругать шаблонную магию C++, но zero-cost abstractions обеспечивает именно она. И пока я в расте не нашёл замены. Ну и остальное (constexpr, int-bool template parameters, etc.). Просто процитирую:

But to be honest it feels much more like Rust is targeting «reasonably fast, safe», then «extremely fast».
В официальной группе по расту в телеграмме(https://t.me/rustlang_ru), есть Никита Кузнецов(@kalloc). Они в Iconic вроде как с HFT работают. Используя при этом Rust. Можешь у него поспрашивать интересующие тебя вопросы.
В официальной группе по расту в телеграмме

Ни телеграмовское, ни rustycrate.ru/gitter сообщества не официальные.

Согласен, уже после отправки подумал, что нужно было написать оф. русское сообщество))) Либо вообще не писать официальное.
Как уже сказали, иконик занимается именно что HFT и именно на расте. На последней конфе очень агрессивно хантили себе в проект, что как бы намекает.

Для Си есть, как минимум, два более-менее живых:



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


Можно тут почитать отчет о недавней попытке использовать для портирования библиотеки: https://wiki.alopex.li/PortingCToRust

Лучше руками. Если либа изначально хорошо написана, проблем с переносом не составит. Если написана криво, можно либо исправить, либо отказаться. А с крупным проектом вообще никакой конвертер не поможет, только бинды.
Конверторов программ с какого-либо алгоритмического языка на Rust.
Есть экспериментальные конверторы из C в Rust: citrus, corrode, c2rust. Разумеется на выходе у них весь код помечен как unsafe.

На мой взгляд, сейчас очень сильна хайп волна от Golang, но на мой взгляд Rust гораздо лаконичней и перспективней. Вопрос, не повлияет ли все таки пресловутая хайп волна на популярность Rust.

Раньше были опасения, что Rust может сильно отстать от Go и Swift из-за отсутствия маркетинговой раскрутки. Но сейчас таких опасений нет, Rust хорошо развивается как язык, появляется все больше интересных проектов, его сообщество постоянно растет. Лично я считаю, что Rust — сильно недооцененная технология. Это "мина замедленного действия", "подрыв" которой — вопрос времени.

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

При чем тут хайп к Go?


Его создали одни из крутейших инженеров в Ай-Ти мире, реально как раз хайпа и маркетинга особого и нету, что ещё пока живы куча бессмысленных вещей типа Python в вебе.


Ну и Go это не системный язык, хоть и компилируется в нативный код. Он не прямой конкурент для Rust. Для Rust, как я понимаю, конкурент C++, так как C он тоже скорее всего не заменит.

Питон как раз на своём месте, а вот Go повис где-то между нишами системного и прикладного программирования и похоже почти никода не нужен.
Статью можно было назвать «10 неочевидных преимуществ функциональных языков программирования». Вот уж действительно, С++, который ощущается как Haskell.
Rust все-таки императивный язык программирования. Он этим и интересен, что привносит в императивное программирование многое из того, что раньше было доступно лишь в функциональных языках. При этом позволяя писать и низкоуровневые вещи, а также не жертвуя эффективностью, с околонулевой стоимостью абстракций и автоматическим управлением памятью без сборки мусора.

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


Обычно говорят что раст императивный, но с вывертом: чистые функциональные языки стараются не допускать общего изменяемого состояния (shared mutable state), запрещая изменения, а Ржавчина запрещает именно одновременность общего и изменяемого состояния. Т.е. в один момент времени состояние может быть или общим (много & указателей на переменную, например), или изменяемым, но с уникальным доступом (&mut указатель требует эксклюзивности доступа). Т.е. цель сходная с чистой функциональщиной, но путь к ней другой из-за необходимости быть более низкоуровневым.


P.S. недавняя статья на тему

неизменяемость переменных по умолчанию

?! * в шоке *
А как мне значение переменной поменять не одну тысячу раз? O_O
Выделять под каждый новый экземпляр переменной новую дополнительную память?! * в ауте *
Скоро можно будет. RFC о юникодных идентификаторах приняли не так давно, срачей вокруг него куча была.

А IDE позволяет отличить русское «с» от латинской «c»?
И вроде, ещё японские и китайские иероглифы имеют разные кодировки в Юникоде при одинаковом внешнем виде. Японские иероглифы — это немного модифицированные в XX веке китайские (не считая японского слогового алфавита), так что большая часть иероглифов выглядит одинаково, но часть иероглифов отличается.

Спокойно, просто вместо let a = 5; надо написать let mut a = 5; и она станет изменяемой.


Про идентификаторы — я подозреваю что в серьезных международных проектах просто будет включено предупреждение об использовании не-ascii идентификаторов (#![forbid(non_ascii_idents)]) и все.

Уф! Напугали!
Хорошо, можно делать изменяемые переменные.
Ну, в каком-нибудь хаскелле их нет. И не поверите, но можно написать полезную программу без единой изменяемой переменной. И там даже массивы неизменяемые, чтобы добавить один элемент (или поменять его), логически копируется все элементы массива, с одним измененным.

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

Поскольку на ФП, я никогда не писал, то спрошу (возможно глупые) вопросы:

Разве копирование не жрёт память? А в случае огромного массива разве затраты времени на копирование нулевые?
Если я собираюсь использовать Rust вместо C++, то у меня вполне реальна ситуация когда свыше сотни раз в секунду будут меняться значения как десятков переменных, так и десятков массивов (какой-нибудь 3D-меш в несколько сот полигонов — вполне себе массив).
Если всё это и так, без ФП, само по себе, жрёт под гигабайт памяти (вместе с текстурами и прочим), то мне как-то очень страшновато переменные копировать, а не изменять. Или в этом случае следует всё же забить на неизменяемость, и использовать изменяемые переменные и массивы?

И что со сборкой мусора в Rust? Как я понимаю, она имеется по причине наличия неизменяемых переменных, или нет?
И если она есть, то её можно отключать?

Уточню, PsyHaSTe говорил о функциональных языках, похожих на Haskell. Rust к ним не относится и не использует оптимизации применяемые функциональными языками при работе с неизменяемыми массивами. Использовать изменяемые массивы в Rust — нормальная практика.


И что со сборкой мусора в Rust?

Сборки мусора нет. В отличие от функциональных языков, где массив может быть реализован как ссылка на предыдущую неизменяемую версию массива + список изменений, массив в Rust — это просто непрерывная область памяти в которой лежат данные, так же как и в C++. Память, выделенная под массив, освобождается при выходе переменной из области видимости или явным вызовом drop(), так же как и в C++.


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


Повторю, никакой магии под капотом в Rust нет: если структура описана как два четырёхбайтовых целых числа, то в памяти будут лежать два четырёхбайтовых целых числа, никаких ссылок на предыдущее неизменяемое значение там не будет, поэтому и не требуется сборщик мусора для очистки неиспользуемых предыдущих значений.

Как выше сказали, я говорил про ФП, где есть различные трюки для того, чтобы неизменяемые данные работали примерно с той же скоростью, что и изменяемые.

Разве копирование не жрёт память? А в случае огромного массива разве затраты времени на копирование нулевые?

Иногда нулевые, иногда нет. Без бенчмарков ничего нельзя сказать, поэтому совет по-умолчанию: пишите максимально просто и понятно, а затем разворачивайте узкие места в более производительные конструкции.

Есть статья, где человек рассказывает, как он с тяжело оптимизированного сишного кода переписал на раст, и получил буст производительности в полтора раза. При этом он писал, насколько я знаю, максимально идеоматично, с неизменяемыми данными (по-возможности) и т.п., а тяжело оптимизированная сишка имела дело с кучей мутабельных буферов (для производительности). Он сам не ожидал таких результатов, но вот так вот вышло. Не забывайте: «Premature optimization is the root of all evil.» © Кнут, без бенчмарков нельзя что-то оптимизировать. Об этом и Фаулер писал, и еще кто-то. В чистом коде, по крайней мере, про это есть, насколько я помню.

И что со сборкой мусора в Rust? Как я понимаю, она имеется по причине наличия неизменяемых переменных, или нет?
И если она есть, то её можно отключать?

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

Круто!
UFO just landed and posted this here

А в Rust можно использовать кириллические имена переменных и функций ?

Разные причины бывают. В основном, удобство. Если вы пишете реализация какого-то алгоритма по научной статье, то иногда бывает удобно переменные так и назвать: ∇λ, σ, и т. п. В случае какого-то регионально-специфичного ПО могут быть какие-то непереводимые понятия, которые проще написать в оригинале, чем извращаться с переводом или транслитерацией латиницей.

Только работать с такими переменными будет неудобно.
Далеко не каждый терминал позволяет удобно вводить их с клавы — значит нужно будет каждый раз Copy+Paste делать. Еще хуже ситуация — вы на маке, коллега на винде. У вас чуть разное отображение шрифтов и разный ввод символов. Брррр.
Не понимаю, зачем из нормального языка делать 1С.

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

Стандартная библиотека языка остаётся ASCII-only и на английском. Авторы RFC советуют другим библиотекам поступать так же, если у них нет других соображений. Так что я бы не назвал это превращением Rust в 1С.
Как выше ответили — уже сейчас можно сделать объявление соответствующей фичи и писать: play.rust-lang.org/?version=nightly&mode=debug&edition=2015&gist=b344b0c2bd40673455a7518af1a7a2a8

Другое дело, что сейчас хотят сделать юникодные идентификаторы по-дефолту (то есть не-юникодные символы надо будет явно запрещать, а не разрешать), с чем я не согласен, но видимо люди не знают, что кто-то может пользоваться клавиатурами, где нельзя набрать лямбду самостоятельно.
зато читать код, в котором вместо разных переменных одинаковые квадратики — сложнее

Не профессиональным программистам так удобнее. Вообще была попытка русифицировать Lua для торгового терминала OUIK — чтобы можно было писать код и на русском и латиницей https://тхаб.рф/wiki/LuaRu
Есть ещё очень профессионально сделанный 1Script http://oscript.io — активно развивается, есть сообщество поддержка MS Visual Studio Code

Помимо хорошей документации стандартной библиотеки можно ещё посмотреть её исходный код, залезть так сказать в мозги и у видеть как это всё работает: списки, хеш-мапы итп. Возможно такое есть и в других языках, но в rust-е исходный код читается достаточно легко.
Rust годный язык для разных задач в том числе и для Web, но в дальнейшем ему надо будет бороться за место под солнцем c одной стороны с С++(С++11, С++14,C++17,C++20)- это со стороны как системного, c другой с набирающем популярность Go — это со стороны Web. Далее — с GUI для десктоп у него пока никак (QT — живее всех живых), бодаться за Web фронт c JavaScript бесполезно(например — Dart как бы есть и как бы нет его).Далее — embedded очень консервативен — тоже С/C++ во всех проектах. Далее — машинное обучение — Python, BigData — Scala/Java/Python ну и по списку можно продолжать.
Ну а то что его на StackOverflow любят — ну так любить не значит жениться.
Короче, тяжело ему будет у работадателей зацепиться.Вакансий реально мало.
Я не был бы так категоричен. Походив по Rust-собеседованиям и поопрашивав собеседователей, услышал забавное — во-первых, альтернативе Rust просто нет для тех бизнес целей (скорость + надежность) изначально, во вторых, опробовав, люди его уже не сменят ни на что — несмотря на рассказы о смертельной кривой обучения (которые разгоняются в основном неосилившими не желающими осилить борроучекер), язык конвертируется в бизнес-выгоду молниеносно, на уровне Java, и это при том, что для многого нет сторонних библиотек, и надо все отсутствующее дописывать с нуля. Дословно, фраза СЕО «I have no single minute of regret about the decision of taking Rust as a primary language, although it holds our production for more than two years already». Да, серьезному бизнесу вроде финтеха прыгать в Rust пока страшно, но процесс освоения рынков идет. Лично я жду, когда веб-разработчикам наконец-то надоест жевать производные джаваскрипта, отдавая браузеру все больше ресурсов, и начнется великое пришествие WebAssembly.

А С++ никуда не денется ближайшее время, потому что то, что можно было у него откусить, уже давно откушено Java и C#, а оставшаяся публика слишком консервативна. В нее никто не целится.
А как у него обстоят дела с IDE? Что-то уровня Idea уже есть?
Почти: intellij-rust.github.io :D

P.S. Не идеален, но работает сносно (у нас проект на ~100 KLOC строк кода). Некоторые функции работают очень медленно (например, «найди мне все реализации этого типажа»).

Есть плагин для IntelliJ IDEA :D Кстати сказать, официальный, от JetBrains (что намекает на перспективы языка). Конечно, до уровня поддержки Java пока далеко, но в целом — отлично работает, умеет почти все, что требуется в повседневной разработке (кроме дебага — для этого потребуется лицензионный CLion или старый-добрый gdb)


Также есть плагин для VS Code — он использует Language Server Protocol. Работает вроде неплохо, но пока что нестабильно и для по-настоящему больших проектов, пожалуй, не подходит.


Разумеется, есть и плагины под другие редакторы — статус поддержки можно посмотреть на https://areweideyet.com

Есть удобный плагин для IDEA.
Да я как раз хотел черкануть(ну упустил из виду), что WebAassembly — это как раз одна из ниш именно Rust.
И еще прикол-после 6 летней спячки проснулся создатель node.js, покаялся в ошибках(хотя кто их не делает ) и уже начал пиарить новый проект deno, а в нем он использует Rust (а хотел Go ), ну и до кучи там C и javascript (внезапно!) typesrcipt(клевый язык — чуствуется рука создателя Delphi и C#).Ну на этом все — пост все таки про Rust.
А как у Rust с научными расчетами? Есть какие-то общепризнанные математические библиотеки для работы с матрицами, математическими функциями вроде функций Бесселя и т.п? И есть ли библиотека для построение 2D- и 3D-графиков?

Интересно, удастся ли полноценно заменить связку Python + numpy + matplotlib на Rust + что-то.
Наиболее развитый проект для матриц это nalgebra, к сожалению, без const generics (которые обещают в начале следующего года в Nightly) данному крейту приходится использовать typenum, что не лучшим образом сказывается на эргономичности. Есть ещё немного заброшенный cgmath, но он сильно проще и создан с расчётом на компьютерную графику, т.е. никаких многомерных матриц.

Для 2D и 3D графиков хороших нативных решений пока нет (и вероятно ещё достаточно долго не будет, т.к. это весьма нетривиальная задача). Большинство либо использует gnuplot, либо дёргает matplotlib через крейты вроде pyo3.
А матрицы там считают с параллелизацией?
Вы про SIMD или про параллелизацию между тредами? Если первое, то пока что приходится полагаться на автовекторизацию, т.к. разработчики решили что явный SIMD сделает код значительно сложнее без достаточного выигрыша (но это может поменяться с добавлением portable SIMD в стандартную библиотеку). Есть ещё биндинги к lapack и GLM. Разумеется есть ещё биндинги Если второе, то вроде бы в определённых случаях можно использовать связку nalgebra+rayon, но подробностей не скажу.
Я использовал бинды к Gnuplot для отрисовки графиков. Без звезд с неба (как например питоновская Панда), но с задачами справляется.
«Модульные тесты в Rust писать настолько легко и просто, что хочется это делать снова и снова. :) Зачастую вам будет проще написать модульный тест, чем пытаться протестировать функциональность другим способом.» и тест тавтологии и 2 + 2. Серьёзно? Вам отчаяно не хватало пунктов до круглого числа или Вы реально думаете что такой пример что-то доказывает?

Пример показывает, что тесты писать просто — нашлепнул на функцию #[test] и готово, остальное встроено в стандартный cargo.

Это пока чего-то более серьёзного не захочется.

До JUnit в Java ещё пока не дотягивает, на мой взгляд. Например, не хватает возможности удобно писать data-driven тесты. Так, чтобы 1 файл с данными = 1 тест, например; с возможностью запуска индивидуальных тестов.

Какие-то движения вокруг custom test frameworks вроде происходят, но, к сожалению, нет времени, чтобы во всём этом разобраться.

Пока выкручиваюсь с proc macro, который сканирует файлы и создаёт #[test] функцию на каждый файл, выглядит примерно так (второй параметр — регулярное выражение для матча на «основной» файл теста, остальные параметры — шаблоны-подстановки по этому регулярному выражению):

// запустится с A.input.txt / A.output.txt, B.input.txt / B.output.txt, и.т.д.
#[datatest("tests/test-cases", r"^(.*)\.input\.txt", r"$1.output.txt")]
fn sample(input: String, output: String) {
  assert_eq!(format!("Hello, {}!", input), output);
}


Можно, конечно, сделать так, чтобы все эти тесты в одной #[test] функции выполнялись, но тогда очень неудобно с этими тестами работать. Например, если делается рефакторинг и половина тестов поломана — хочется возможности запустить один тест, чтобы начать с чего-то мелкого. Плюс, хочется видеть все поломки, а не первую. И так далее.

Может быть, можно как-то через кишки модуля test выкрутиться?..

P.S. Похоже, можно попробовать через «harness = false» в Cargo.toml. Пора снова переписывать фреймворк :D

Согласен, очень не хватает параметризованных тестов. Приходится извращаться в стиле


#[cfg(test)]
mod tests {
    use super::*;

    macro_rules! generate_test {
        ($name:ident, $c:expr, $a:expr, $b:expr, $result:expr) =>  {
            #[test]
            fn $name() {
                assert_eq!(are_concatable_strings($a, $b, $c), $result);
            }
        }
    }

    generate_test!(a, "javascript", "javpt", "ascri", true);
    generate_test!(b, "javascript", "jasrit", "vacp", true);
    generate_test!(c, "javascript", "java", "scripts", false);
    generate_test!(d, "javascript", "jav", "script", false);
    generate_test!(e, "java not script", "java ", "not script", true);
    generate_test!(f, "javascript", "java ", "scritp", false);
    generate_test!(g, "Java1Java3Java2Java4", "Java2Java4", "Java1Java3", true);
    generate_test!(h, "jjaaamaaat", "jaaam", "jaaat", false);
}

В таком простом случае не ясно, зачем вам несколько тестовых функций, когда можно обойтись всего одной. Тут ведь проверяется корректность всего одной функции are_concatable_strings(), принимающей короткие значения:


#[test]
fn strings_concat_fine() {
    assert!(are_concatable_strings("javascript", "javpt", "ascri"));
    assert!(!are_concatable_strings("javascript", "java", "scripts"));
    // ...
}

Потому что в таком случае если тесты не пройдут я сразу пойму, какие условия не выполнены


running 9 tests
test tests::a ... ok
test tests::b ... ok
test tests::c ... ok
test tests::d ... ok
test tests::e ... ok
test tests::f ... ok
test tests::g ... ok
test tests::h ... FAILED
test tests::m ... FAILED

failures:

---- tests::h stdout ----
thread 'tests::h' panicked at 'assertion failed: `(left == right)`
  left: `false`,
 right: `true`', src/lib.rs:61:5
note: Run with `RUST_BACKTRACE=1` for a backtrace.

---- tests::m stdout ----
thread 'tests::m' panicked at 'assertion failed: `(left == right)`
  left: `false`,
 right: `true`', src/lib.rs:62:5

failures:
    tests::h
    tests::m

test result: FAILED. 7 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out

А в этом случае просто будет "Отвалилось на 10 строчке" и ищи свищи. Причем непонятно, 11 строчка и дальше — работают или нет.

"Отвалилось на 10 строчке" и ищи свищи

Не только. Еще будет распечатано содержимое assert'а (если использовался обычный assert).

Не только. Еще будет распечатано содержимое assert'а (если использовался обычный assert).

Ага, как в примере выше :)
thread 'tests::h' panicked at 'assertion failed: `(left == right)`
left: `false`,
right: `true`', src/lib.rs:61:5
note: Run with `RUST_BACKTRACE=1` for a backtrace.


Ну и это не отменяет того, что я все еще остановлюсь на первой же ошибке. тогда как множество всех отвалившихся тестов помогает понять, что пошло не так.
В примере выше используется assert_eq, а не обычный assert )
Я согласен с PsyHaSte. У меня было 3 AST дерева. Одно общие, другие являются конвертируемым в третье. И вот нужно проверить определенные вещи. И я использовал сначала первый подход. И пусть я даже в цикле печатал в случае ошибки дополнительную информацию о том какое дерево сейчас конвертируется. Я все ровно натыкался иногда на то что случайно думал что я еще фикшу первый баг, когда ошибка уже возникает в другом месте. Просто я вижу assert вижу результат он примерно тот который я ожидаю, но он для другого дерева, в котором я не думал что будет проблема. По этому решение когда запускается 100 тестов из которых 12 упало, лучше, чем когда у тебя всегда один который всегда не проходит, пока ты внедряешь новую фичу.

Но за это я раст и люблю, что в нем есть макросы, которые позволяют решить все раздражающие моменты. Нужен сахар, вот тебе макрос, пиши какой хочешь)))
Этот пример показывает, что в Rust есть встроенная поддержка базовых механизмов тестирования, и что тесты писать очень просто. Большинство тестов именно так и пишутся. Если вам нужны моки, или рандомная генерация тестовых наборов, или еще что-то дополнительно — всегда можно использовать внешние библиотеки для этого. Но опять же, чаще вам нужно просто удостовериться, что ваша функция (или метод) корректно работают на нескольких основных и крайних значениях. Легковесные тесты тут выручают и вас, и тех, кто потом будет читать ваш код, так как они сразу получат примеры его использования.
Sign up to leave a comment.

Articles