10 неочевидных преимуществ использования Rust

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



    Все это уже хорошо известно всем, кто хоть немного следит за развитием современных технологий программирования. Но что если вы не системный программист, да и многопоточного кода в ваших проектах не много, но вас все же привлекает производительность Rust'а. Получите ли вы какие-то дополнительные преимущества от его использования в прикладных задачах? Или все, что он вам даст дополнительно — это суровую борьбу с компилятором, который будет заставлять вас писать программу так, чтобы она неотступно следовала правилам языка по заимствованию и владению?


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


    1. Универсальность языка


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


    Давайте рассмотрим несколько простых примеров использования Rust.


    Пример совмещения двух итераторов в один итератор по парам элементов:


    let zipper: Vec<_> = (1..).zip("foo".chars()).collect();
    
    assert_eq!((1, 'f'), zipper[0]);
    assert_eq!((2, 'o'), zipper[1]);
    assert_eq!((3, 'o'), zipper[2]);

    Запустить


    Примечание: вызов формата name!(...) — это вызов функционального макроса. Имена таких макросов в Rust всегда заканчиваются символом !, чтобы их можно было отличить от имен функций и прочих идентификаторов. О преимуществах использования макросов еще будет сказано ниже.

    Пример использования внешней библиотеки regex для работы с регулярными выражениями:


    extern crate regex;
    
    use regex::Regex;
    
    let re = Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();
    assert!(re.is_match("2018-12-06"));

    Запустить


    Пример реализации типажа Add для собственной структуры Point, чтобы перегрузить оператор сложения:


    use std::ops::Add;
    
    struct Point {
        x: i32,
        y: i32,
    }
    
    impl Add for Point {
        type Output = Point;
    
        fn add(self, other: Point) -> Point {
            Point { x: self.x + other.x, y: self.y + other.y }
        }
    }
    
    let p1 = Point { x: 1, y: 0 };
    let p2 = Point { x: 2, y: 3 };
    
    let p3 = p1 + p2;

    Запустить


    Пример использования обобщенного типа в структуре:


    struct Point<T> {
        x: T,
        y: T,
    }
    
    let int_origin = Point { x: 0, y: 0 };
    let float_origin = Point { x: 0.0, y: 0.0 };

    Запустить


    На Rust вы можете писать эффективные системные утилиты, большие настольные приложения, микросервисы, веб-приложения (включая клиентскую часть, так как Rust можно скомпилировать в Wasm), мобильные приложения (хотя в этом направлении экосистема языка пока развита слабо). Такая универсальность может оказаться преимуществом для многопроектных команд, потому что она позволяет использовать одинаковые подходы и одни и те же модули во множестве разных проектов. Если вы привыкли к тому, что каждый инструмент предназначен для своей узкой области применения, то попробуйте посмотреть на Rust как на ящик с инструментами одинаковой надежности и удобства. Возможно, вам именно этого и не хватало.


    2. Удобные инструменты сборки и управления зависимостями


    Это явно не рекламируется, но многие замечают, что в Rust реализована одна из лучших на сегодняшний день система сборки и управления зависимостями. Если вы программировали на С или С++, и вопрос безболезненного использования внешних библиотек стоял для вас достаточно остро, то использование Rust с его инструментом сборки и менеджером зависимостей Cargo будет хорошим выбором для ваших новых проектов.


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


    Конфигурационный файл Cargo использует для описания настроек проекта дружелюбный и минималистичный язык разметки toml. Вот пример типичного файла конфигурации Cargo.toml:


    [package]
    name = "some_app"
    version = "0.1.0"
    authors = ["Your Name <you@example.com>"]
    
    [dependencies]
    regex = "1.0"
    chrono = "0.4"
    
    [dev-dependencies]
    rand = "*"

    А ниже приведены три типичные команды использования Cargo:


    $ cargo check
    $ cargo test
    $ cargo run

    С их помощью будет произведена проверка исходного кода на ошибки компиляции, сборка проекта и запуск тестов, сборка и запуск программы на выполнение, соответственно.


    3. Встроенные тесты


    Модульные тесты в Rust писать настолько легко и просто, что хочется это делать снова и снова. :) Зачастую вам будет проще написать модульный тест, чем пытаться протестировать функциональность другим способом. Вот пример функций и тестов к ним:


    pub fn is_false(a: bool) -> bool {
        !a
    }
    
    pub fn add_two(a: i32) -> i32 {
        a + 2
    }
    
    #[cfg(test)]
    mod test {
        use super::*;
    
        #[test]
        fn is_false_works() {
            assert!(is_false(false));
            assert!(!is_false(true));
        }
    
        #[test]
        fn add_two_works() {
            assert_eq!(1, add_two(-1));
            assert_eq!(2, add_two(0));
            assert_eq!(4, add_two(2));
        }
    }

    Запустить


    Функции в модуле test, помеченные атрибутом #[test], являются модульными тестами. Они будут выполняться параллельно при вызове команды cargo test. Атрибут условной компиляции #[cfg(test)], которым помечен весь модуль с тестами, приведет к тому, что модуль будет компилироваться только при выполнении тестов, а в обычную сборку не попадет.


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


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


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


    4. Хорошая документация с актуальными примерами


    Стандартная библиотека Rust очень хорошо документирована. Html-документация генерируется автоматически по исходному коду с markdown-описаниями в док-комментариях. Более того, док-комментарии в коде на Rust содержат примеры кода, которые исполняются во время запуска тестов. Этим гарантируется актуальность примеров:


    /// Returns a byte slice of this `String`'s contents.
    ///
    /// The inverse of this method is [`from_utf8`].
    ///
    /// [`from_utf8`]: #method.from_utf8
    ///
    /// # Examples
    ///
    /// Basic usage:
    ///
    /// ```
    /// let s = String::from("hello");
    ///
    /// assert_eq!(&[104, 101, 108, 108, 111], s.as_bytes());
    /// ```
    #[inline]
    #[stable(feature = "rust1", since = "1.0.0")]
    pub fn as_bytes(&self) -> &[u8] {
        &self.vec
    }

    Документация


    Здесь пример использования метода as_bytes у типа String


    let s = String::from("hello");
    
    assert_eq!(&[104, 101, 108, 108, 111], s.as_bytes());

    будет выполнен как тест во время запуска тестов.


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


    5. Умное автовыведение типов


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


    let mut vec = Vec::new();
    let text = "Message";
    vec.push(text);

    Запустить


    Если мы расставим аннотации типов, то данный пример будет выглядеть так:


    let mut vec: Vec<&str> = Vec::new();
    let text: &str = "Message";
    vec.push(text);

    То есть мы имеем вектор строковых срезов и переменную типа строковый срез. Но в данном случае указывать типы совершенно излишне, так как компилятор их может вывести сам (пользуясь расширенной версией алгоритма Хиндли — Милнера). То, что vec — это вектор, уже понятно по типу возвращаемого значения из Vec::new(), но пока не понятно, какой будет тип его элементов. То, что тип text — это строковый срез, понятно по тому, что ему присваивается литерал именно такого типа. Таким образом, после vec.push(text) становится очевидным и тип элементов вектора. Обратите внимание, что полностью тип переменной vec был определен ее использованием в потоке выполнения, а не на этапе инициализации.


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


    Конечно, мы не можем польностью избавиться от указания типов в статически типизированном языке. В программе должны быть точки, в которых типы объектов гарантированно известны, чтобы в других местах можно было эти типы выводить. Такими точками в Rust являются объявления пользовательских типов данных и сигнатуры функций, в которых нельзя не указывать используемые типы. Но в них можно вводить "метапеременные типов", при использовании обобщенного программирования.


    6. Сопоставление с образцом в местах объявления переменных


    Операция let


    let p = Point::new();

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


    let Point { x, y } = Point::new();

    Запустить


    Здесь произведена деструктуризация: такое сопоставление введет переменные x и y, которые будут проинициализированы значением полей x и y объекта структуры Point, который возвращается вызовом Point::new(). При этом сопоставление корректное, так как типу выражения справа Point соответствует образец типа Point слева. Похожим образом можно взять, например, два первых элемента массива:


    let [a, b, _] = [1, 2, 3];

    И сделать много чего еще. Самое замечательное, что подобного рода сопоставления производятся во всех местах, где могут вводиться новые имена переменных в Rust, а именно: в операторах match, let, if let, while let, в заголовке цикла for, в аргументах функций и замыканий. Вот пример элегантного использования сопоставления с образцом в цикле for:


    for (i, ch) in "foo".chars().enumerate() {
        println!("Index: {}, char: {}", i, ch);
    }

    Запустить


    Метод enumerate, вызванный у итератора, сконструирует новый итератор, который будет перебирать не исходные значения, а кортежи, пары "порядковый индекс, исходное значение". Каждый из этих кортежей при итерациях цикла будет сопоставляться с указанным образцом (i, ch), в результате чего переменная i получит первое значение из кортежа — индекс, а переменная ch — второе, то есть символ строки. Далее в теле цикла мы можем использовать эти переменные.


    Другой популярный пример использования образца в цикле for:


    for _ in 0..5 {
        // Тело выполняется 5 раз
    }

    Здесь мы просто игнорируем значение итератора, используя образец _. Потому что номер итерации в теле цикла мы никак не используем. То же самое можно сделать, например, с аргументом функции:


    fn foo(a: i32, _: bool) {
        // Второй аргумент никогда не используется
    }

    Или при сопоставлении в операторе match:


    match p {
        Point { x: 1, .. } => println!("Point with x == 1 detected"),
        Point { y: 2, .. } => println!("Point with x != 1 and y == 2 detected"),
        _ => (), // Ничего не делаем во всех остальных случаях
    }

    Запустить


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


    7. Расширение синтаксиса и пользовательские DSL


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


    println!("Hello, {name}! Do you know about {}?", 42, name = "User");

    Помимо того, что данный макрос расширияет синтаксические возможности вызова "функции" печати форматированной строки, он еще будет в своей реализации проверять соответствие входных аргументов указанной строке формата во время компиляции, а не во время выполнения. С помощью макросов вы можете вводить лаконичный синтаксис под ваши собственные проектные нужды, создавать и использовать DSL. Вот пример использования кода на JavaScript внутри Rust-программы, компилирующейся в Wasm:


    let name = "Bob";
    let result = js! {
        var msg = "Hello from JS, " + @{name} + "!";
        console.log(msg);
        alert(msg);
        return 2 + 2;
    };
    println!("2 + 2 = {:?}", result);

    Макрос js! определен в пакете stdweb и он позволяет встраивать полноценный JavaScript-код в вашу программу (за исключением строк в одинарных кавычках и операторов, не завершенных точкой с запятой) и использовать в нем объекты из Rust-кода с помощью синтаксиса @{expr}.


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


    8. Автогенерация зависимого кода


    Процедурные derive-макросы в Rust широко используются для автоматической реализации типажей и прочей кодогенерации. Вот пример:


    #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
    struct Point {
        x: i32,
        y: i32,
    }

    Так как все эти типажи (Copy, Clone, Debug, Default, PartialEq и Eq) из стандартной библиотеки реализованы для типа полей структуры i32, то и для всей структуры в целом их реализация может быть выведена автоматически. Другой пример:


    extern crate serde_derive;
    extern crate serde_json;
    
    use serde_derive::{Serialize, Deserialize};
    
    #[derive(Serialize, Deserialize)]
    struct Point {
        x: i32,
        y: i32,
    }
    
    let point = Point { x: 1, y: 2 };
    
    // Сериализация Point в JSON строку.
    let serialized = serde_json::to_string(&point).unwrap();
    
    assert_eq!("{\"x\":1,\"y\":2}", serialized);
    
    // Десериализация JSON строки в Point.
    let deserialized: Point = serde_json::from_str(&serialized).unwrap();

    Запустить


    Здесь с помощью derive-макросов Serialize и Deserialize из библиотеки serde для структуры Point автоматически генерируются методы ее сериализации и десериализации. Дальше можно передавать экземпляр этой структуры в различные функции сериализации, например, преобразующие его в JSON строку.


    Вы можете создавать собственные процедурные макросы, которые сгенерируют нужный вам код. Либо пользоваться множеством уже созданных макросов другими разработчиками. Помимо избавления программиста от написания шаблонного кода, у макросов есть еще то преимущество, что вам не нужно поддерживать в согласованном состоянии разные участки кода. Скажем, если в структуру Point будет добавлено третье поле z, то для обеспечения ее корректной сериализации в случае использования derive ничего делать больше не нужно. Если же мы будем сами реализовывать необходимые типажи для сериализации Point, то нам придется следить за тем, чтобы эта реализация всегда была согласована с последними изменениями в структуре Point.


    9. Алгебраический тип данных


    Алгебраический тип данных, говоря упрощенно — это составной тип данных, являющийся объединением структур. Более формально — это тип-сумма из типов-произведений. В Rust такой тип определяется с помощью ключевого слова enum:


    enum Message {
        Quit,
        ChangeColor(i32, i32, i32),
        Move { x: i32, y: i32 },
        Write(String),
    }

    Тип конкретного значения переменной типа Message может быть только одним из перечисленных в Message типов-структур. Это либо unit-подобная структура Quit без полей, либо одна из кортежных структур ChangeColor или Write с безымянными полями, либо обычная структура Move. Традиционный перечислимый тип может быть представлен как частный случай алгебраического типа данных:


    enum Color {
        Red,
        Green,
        Blue,
        White,
        Black,
        Unknown,
    }

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


    let color: Color = get_color();
    let text = match color {
        Color::Red => "Red",
        Color::Green => "Green",
        Color::Blue => "Blue",
        _ => "Other color",
    };
    println!("{}", text);
    
    ...
    
    fn process_message(msg: Message) {
        match msg {
            Message::Quit => quit(),
            Message::ChangeColor(r, g, b) => change_color(r, g, b),
            Message::Move { x, y } => move_cursor(x, y),
            Message::Write(s) => println!("{}", s),
        };
    }

    Запустить


    В виде алгебраических типов данных в Rust реализованы такие важные типы, как Option и Result, которые используются для представления отсутствующего значения и корректного/ошибочного результата, соответственно. Вот как определяется Option в стандартной библиотеке:


    pub enum Option<T> {
        None,
        Some(T),
    }

    В Rust отсутствует null-значение, ровно как и досадные ошибки непредвиденного обращения к нему. Вместо этого там, где действительно необходимо указать возможность отсутствия значения, используется Option:


    fn divide(numerator: f64, denominator: f64) -> Option<f64> {
        if denominator == 0.0 {
            None
        } else {
            Some(numerator / denominator)
        }
    }
    
    let result = divide(2.0, 3.0);
    match result {
        Some(x) => println!("Result: {}", x),
        None => println!("Cannot divide by 0"),
    }

    Запустить


    Алгебраический тип данных — достаточно мощный и выразительный инструмент, который открывает дверь в Type-Driven Development. Грамотно написанная программа в этой парадигме возлагает на систему типов большую часть проверок корректности своей работы. Поэтому если вам нехватает немного Haskell в повседневном промышленном программировании, Rust может стать вашей отдушиной. :)


    10. Легкий рефакторинг


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




    Пожалуй это все, о чем я хотел рассказать в этой статье. Конечно, у Rust есть еще много других достоинств, а также имеется ряд недостатков (некоторая сырость языка, отсутствие привычных идиом программирования, "нелитературный" синтаксис), о которых здесь не упоминается. Если вам есть, что о них рассказать — напишите в комментариях. А вообще, опробуйте Rust на практике. И может быть его достоинства для вас перевесят все его недостатки, как это произошло в моем случае. И вы, наконец, получите именно тот набор инструментов, в котором долго нуждались.

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

    Используете ли вы Rust в своих проектах?
    Поделиться публикацией

    Похожие публикации

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

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

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


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


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


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

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

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

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


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


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

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


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


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

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

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

                  Сдаётся мне, что пока я просто или не до конца понимаю как использовать Rust для прикладного программирования, или просто неправильно его использую.
                    +3
                    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) Это означает что ООП подход сильно повлиял на ваш стиль мышления (импринтинг), и что, как уже написали, вам нужно ломать свои привычки и учиться смотреть на задачи с другой стороны. Дело нелёгкое и для многих весьма неприятное, но в целом полезное.
                      +2
                      1) В том-то и дело, что модель уже не получается такой чистой, если в ней будут какий-то опциональные поля. А если мне скажем нужно размапить 5-10 разных структур, которые представляют собой различные комбинации каких двух-трёх базовых? Предлагаете вводить одну большую структуру на все случаи жизни?
                      2) Спасибо посмотрю, действительно ли это то, что я хочу.
                      3) Да, согласен, не помешает.
                        +2
                        1. А что, если структуры у вас разойдутся по полям? Почти всегда лучше выделить другую структуру обычным копи-пастом. Потому как сегодня общих полей — 2, а завтра — 3. Если просто так совпали звезды и структуры похожи, то это не повод переиспользовать их структуру, это можно быть дорогой в никуда (примерно, как наследовать Point3D : Point2D { public int Z }). В крайнем случае можно просто написать макрос в стиле "скопируй поля вот той структуры". Мерзковато, но возможно.
                        2. Первый трейт называется Bar, опечатка.
                        3. Ответил ниже.
                        0

                        К слову об 1. А в расте можно сделать что-то вроде такого?


                        {-# LANGUAGE DataKinds, GADTs #-}
                        {-# LANGUAGE StandaloneDeriving #-}
                        
                        data StructType = Reduced | Full
                        
                        data FullField sty ty where
                          FieldExists :: ty -> FullField 'Full ty
                          FieldEmpty :: FullField 'Reduced ty
                        
                        deriving instance Eq ty => Eq (FullField sty ty)
                        deriving instance Ord ty => Ord (FullField sty ty)
                        deriving instance Show ty => Show (FullField sty ty)
                        
                        data Struct sty = Struct
                          { name :: String
                          , age :: Int
                          , salary :: Int
                          , dept :: FullField sty String
                          , smthElse :: FullField sty Int
                          } deriving (Eq, Ord, Show)
                        
                        reduced :: Struct 'Reduced
                        reduced = Struct "meh" 20 42 FieldEmpty FieldEmpty
                        
                        full :: Struct 'Full
                        full = Struct "meh" 20 42 (FieldExists "IT") (FieldExists 10)

                        Ну, чтобы статически знать, есть там дополнительные поля или нет.

                          0

                          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 будут одного и того же типа, со всеми вытекающими.

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

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

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

                        В общем, с точки зрения образования оно того стоит. ООП это не панацея, просто набор практик, хороший, но не единственный. Тот же Expression Problem очень неприятен при расширении иерарахии. В итоге в ООП мире обычно все сводится к заучиванию паттерна визитор и все. А в языках с богатыми типами возможно вообще оптимальное решение задачи с помощью tagless final. А вот в ООП оно реализуется, увы, костыльно, хотя и не нереально. Тут подробнее.
                        0
                        Строго говоря, вы не можете наследовать методы между типажами. Да и зачем это нужно? Необходимости в этом у меня не возникало.

                        Но вы знаете, что реализующий трейт FooBar тип реализует также Foo. Протайпчекается ли обобщённый код, принимающий FooBar и вызывающий на нём foo?

                          +1

                          Как ни странно, не только пройдёт проверку, но даже и не потребует явным образом импортировать 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);
                          }
                            0

                            Тут есть небольшая проблема, если я правильно понимаю, &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).

                              0

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

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

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

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


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

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


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


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


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

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

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

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

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

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


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


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


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

                            0
                            80% статьи я написал за один вечер. А оставшиеся 20% растянулись на два дня :)
                            +4
                            До 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 — личные.
                              +1
                              вакансий мало, но мы в эрливидео возможно скоро будем ещё набирать на Rust
                                0
                                Не против продолжить в личке общение на эту тему, если вам интересно (Мск либо удалёнка).
                                  0
                                  можете мне написать на hire@flussonic.com?

                              +3
                              Я в основном работаю с Питоном и чуть со скалой, но все домашние проекты стараюсь на расте делать. К сожалению вакансий на расте немного, хотя ситуация развивается достаточно оптимистично
                                +14

                                Писал на 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 всё же короче.

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

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

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

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

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

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

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

                                  –8
                                  Точка с запятой после каждой строки, как в устаревших языках из 90х, стала для меня непреодолимой преградой.
                                    +6

                                    У точки с запятой, однако, есть и свои синтаксические преимущества. Так что в Rust это не просто бесполезный рудимент.

                                      +9
                                      Без завершающих символов код конечно выглядит лаконичнее, но это же ограничивает форматирование. Приходится «сражаться» с парсером, чтобы тот понял где должны заканчиваться выражения и чтобы код выглядел и хорошо.
                                      0
                                      Что там в Расте с ООП?
                                        +1

                                        У mkpankov есть об этом вопросе заметка на rustycrate — https://rustycrate.ru/обучение/2017/06/11/oop-in-rust.html — и есть целая глава в растбуке — https://doc.rust-lang.org/book/second-edition/ch17-00-oop.html (перевод).

                                          +2
                                          ООП как такового в Rust нет (об этом упоминается в статье). Rust использует параметрический полиморфизм, а вместо наследования данных предлагается использовать аггрегацию. В таком случае смешивать наследование и полиморфизм подтипов, как зачастую происходит в ООП, не получится.
                                            +4

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

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

                                              А можно примеры? Прям чтоб чтот крупное. А то с ходу нагуглися только проект Мозиллы Servo, но это и понятно.
                                                +2
                                                Parity, actix, chalk — первое, что в голову пришло.
                                                  +1

                                                  Примеры можно посмотреть здесь.

                                                    +4
                                                      0

                                                      alacritty — очень быстрый терминал на расте, для Линукс и МакОс

                                                      0
                                                      А есть ли уже сборник шаблонов проектирования или best practices?
                                                      Я периодически мониторю реддит — вопросы о таковых всплывают регулярно, ответы — в стиле ниже, «откройте крупный проект и смотрите».
                                                        +1
                                                        Из тех, что я видел — пребывают в заброшенном состоянии. Сейчас, видимо, период практических экспериментов, теоретические осмысления появятся позже. Поэтому приходится высматривать паттерны в исходных кодах популярных библиотек.
                                                          +2
                                                          Наверное это одна из основных причин, по которым до сих пор нет вменяемой UI библиотеки. Там надо четко просматриваемые шаблоны расширения/переиспользования/переопределения функционала.
                                                            0

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

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

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

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

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

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

                                                            0
                                                            Я пока в Расте совсем новичок, но вот нашёл тут на реддите очень интересный комментарий от человека, как и я, работающего в 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».
                                                              0
                                                              В официальной группе по расту в телеграмме(https://t.me/rustlang_ru), есть Никита Кузнецов(@kalloc). Они в Iconic вроде как с HFT работают. Используя при этом Rust. Можешь у него поспрашивать интересующие тебя вопросы.
                                                                0
                                                                В официальной группе по расту в телеграмме

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

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

                                                              Конвенторов из чего во что?

                                                                +2

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



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


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

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

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

                                                                      +2

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

                                                                        0

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

                                                                        –8

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


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


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

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

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


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


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

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

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

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

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


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

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

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

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

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

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

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


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

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


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


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

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

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

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

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

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

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

                                                                                              Круто!
                                                                                    +1
                                                                                    чистые функциональные языки стараются не допускать общего изменяемого состояния (shared mutable state), запрещая изменения

                                                                                    Не совсем так. Чистые функциональные языки стараются явно выражать и разграничивать эффекты. Никто не мешает вам иметь общее состояние или изменяемое состояние вроде тех же ST, STM или вообще IO. Просто их надо явно указывать.

                                                                                0

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

                                                                                  0

                                                                                  Скоро можно будет. RFC о юникодных идентификаторах приняли не так давно, срачей вокруг него куча была.

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

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

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

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

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

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

                                                                                        +3
                                                                                        Помимо хорошей документации стандартной библиотеки можно ещё посмотреть её исходный код, залезть так сказать в мозги и у видеть как это всё работает: списки, хеш-мапы итп. Возможно такое есть и в других языках, но в rust-е исходный код читается достаточно легко.
                                                                                          0
                                                                                          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 любят — ну так любить не значит жениться.
                                                                                          Короче, тяжело ему будет у работадателей зацепиться.Вакансий реально мало.
                                                                                            +5
                                                                                            Я не был бы так категоричен. Походив по 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#, а оставшаяся публика слишком консервативна. В нее никто не целится.
                                                                                            +1
                                                                                            А как у него обстоят дела с IDE? Что-то уровня Idea уже есть?
                                                                                              +4
                                                                                              Почти: intellij-rust.github.io :D

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

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


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


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

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

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

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

                                                                                                          https://youtu.be/n-txrCMvdms
                                                                                                          0
                                                                                                          «Модульные тесты в Rust писать настолько легко и просто, что хочется это делать снова и снова. :) Зачастую вам будет проще написать модульный тест, чем пытаться протестировать функциональность другим способом.» и тест тавтологии и 2 + 2. Серьёзно? Вам отчаяно не хватало пунктов до круглого числа или Вы реально думаете что такой пример что-то доказывает?
                                                                                                            +2

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

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

                                                                                                              До 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
                                                                                                                +1

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


                                                                                                                #[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);
                                                                                                                }
                                                                                                                  0

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


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

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


                                                                                                                    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 строчка и дальше — работают или нет.

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

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

                                                                                                                        0
                                                                                                                        Не только. Еще будет распечатано содержимое 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.


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

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

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

                                                                                                              Самое читаемое