Что делает Rust универсальным языком программирования

    Владей и заимствуй


    Долгое время Rust позиционировался исключительно как язык для системного программирования. Попытки использовать Rust для высокоуровневых прикладных задач зачастую вызывали усмешку у значительной части сообщества: зачем использовать инструмент в том качестве, на которое он не рассчитан? Какая польза от возни с типами и анализатором заимствований (borrow checker), если есть Python и Java со сборкой мусора? Но другая часть сообщества всегда видела потенциал Rust именно как языка прикладного, и даже находила его удобным в использовании для быстрого прототипирования — во многом благодаря его особенностям, а не вопреки им.


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


    Rust is a systems programming language that runs blazingly fast, prevents segfaults, and guarantees thread safety.

    Rust — невероятно быстрый язык для системного программирования без segfault'ов и с гарантиями потокобезопасности.

    на такое:


    A language empowering everyone to build reliable and efficient software.

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

    Думаю, это хорошая иллюстрация смещения акцентов в позиционировании языка.


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


    Что понимать под высоким и низким уровнем?


    Понятия высокого/низкого уровня, применительно к языкам программирования, уже давно перестали носить абсолютный характер. По сравнению с ассемблером, язык Си — высокоуровневый, а по сравнению с Haskell — низкоуровневый. В случае с Rust ситуация усугубляется тем, что некоторые языковые возможности в нем близки к Haskell, а некоторые — к Си. Чтобы не запутаться, что считать высоким уровнем, а что низким, я предлагаю использовать простое правило: если языковая конструкция скорее выражает что мы делаем в терминах самой задачи, то она более высокого уровня, чем та, которая скорее говорит нам как именно реализуется решение.


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


    unsafe-блоки


    Давайте сразу обратимся к примеру и посмотрим, как в Rust работают итераторы:


    let a: Vec<_> = [1, 2, 3].iter().filter(|&i| i % 2 != 0).map(|i| i * 2).collect();

    запустить


    Здесь мы создаем массив из трех элементов, затем получаем итератор на него и добавляем к нему два адаптера — filter и map, после чего собираем итератор в новую коллекцию типа Vec. Этот код — достаточно высокоуровневый и довольно типичный для многих высокоуровневых языков (таких как JavaScript). Но давайте теперь посмотрим, как реализован объект типа Iter, который мы получаем в результате вызова метода .iter():


    pub struct Iter<'a, T: 'a> {              // 1
        ptr: NonNull<T>,                      // 2
        end: *const T,                        // 3
        _marker: marker::PhantomData<&'a T>,  // 4
    }
    
    impl<'a, T> Iterator for Iter<'a, T> {
        type Item = &'a T;
    
        #[inline]
        fn next(&mut self) -> Option<&'a T> {
            unsafe {                          // 5
                assume(!self.ptr.as_ptr().is_null());
                if mem::size_of::<T>() != 0 {
                    assume(!self.end.is_null());
                }
                if is_empty!(self) {
                    None
                } else {
                    Some(next_unchecked!(self))
                }
            }
        }
    
        ...
    }

    std::slice::Iter


    Обратите внимание, что структура Iter содержит в качестве своих полей два указателя: ptr и end (строки 2 и 3). Из-за того, что эти указатели — это обычные Си-совместимые указатели (правда NonNull дополнительно требует, чтобы указатель не был нулевым), довольно низкоуровневые ссылочные типы, их время жизни никак не отслеживается borrow checker'ом. Поэтому заданное в объявлении структуры время жизни ссылки 'a (1) мы вынуждены добавить в "фантомное" поле с типом нулевой размерности PhantomData<&'a T> (4). Иначе время жизни окажется никак не используемым внутри структуры, что приведет к ошибке компиляции. То есть, другими словами: мы хотим сделать безопасный итератор, который ссылается на элементы коллекции, по которой он итерируется, и для того, чтобы он был безопасным, нам нужно учитывать время жизни ссылок. Но наша внутренняя реализация основана на указателях и потому не подразумевает никакого отслеживания времен жизни со стороны компилятора. Поэтому мы должны гарантировать своей реализацией безопасность кода, работающего с указателями (в unsafe-блоках, подобных 5), и тогда можно реализовать безопасный внешний API по всем правилам работы в safe Rust.


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


    Важный вывод, к которому мы здесь приходим, состоит в том, что Rust — самодостаточный язык. Его высокоуровневые возможности вполне реализуются на низком уровне им же самим. И наоборот: из низкоуровневых "кирпичей" в Rust можно конструировать высокоуровневые блоки, скрывая детали реализации за безопасным API.


    Теперь должно быть понятно, что unsafe, который тут и там встречается в стандартной библиотеке Rust — это не баг, а фича. Есть довольно популярный упрек к Rust со стороны: дескать, какой же это безопасный и высокоуровневый язык, если у него в std сплошные unsafe-блоки? Он либо тогда должен быть весь unsafe, либо полностью safe. Но преимущество Rust как раз состоит в том, что он позволяет делать и то и другое, при этом отделяя одно от другого. Это одна из причин, почему Rust по-настоящему универсальный язык программирования.


    Макросы


    Посмотрите, как организуется простейший цикл for на Python:


    for x in range(5):
      print(x)

    Сравните с Rust:


    for x in 0..5 {
        println!("{}", x);
    }

    запустить


    Они похожи, не правда ли? Но for в Rust — это просто синтаксический сахар к более низкоуровневому представлению. Вот во что разворачивается данный цикл for:


    match IntoIterator::into_iter(0..5) {
        mut iter => loop {
            let next;
            match iter.next() {
                Some(val) => next = val,
                None => break,
            };
            let x = next;
            let () = {
                println!("{}", x);
            };
        },
    }

    for Loops and IntoIterator


    Отсюда понятно, что на место диапазона 0..5 в исходном for можно подставить любое значение, которое реализует трейт IntoIterator, а на место x — все, что допустимо в левой части инструкции let. Зная это, давайте реализуем с помощью макроса собственную версию цикла for, которая будет возвращать значение своего тела на последней итерации как опциональный результат всего цикла:


    macro_rules! foret {
        ($x:pat in $exp:expr => $body:expr) => {{
            let mut result = None;
            match IntoIterator::into_iter($exp) {
                mut iter => loop {
                    let next;
                    match iter.next() {
                        Some(val) => next = val,
                        None => break result,
                    };
                    let $x = next;
                    result = Some($body);
                },
            }
        }}
    }

    let result = foret!(x in 0..5 => {
        println!("{}", x);
        x
    });
    
    assert_eq!(result, Some(4));

    запустить


    Макросы в Rust — декларативные и процедурные — позволяют строить синтаксические абстракции для собственных высокоуровневых предметно-ориентированных языков. В данном случае мы создали декларативный макрос foret. Такие макросы являются гигиеничными и они устроены достаточно просто, но эта простота накладывает некоторые ограничения на допустимый синтаксис: после фрагмента expr (который сопоставляется любому допустимому в Rust выражению) мы должны обязательно поставить разделитель, поэтому там и появилась стрелочка =>. Процедурные макросы избавлены от подобных ограничений, они могут работать с любым синтаксисом, но они чуть сложнее в реализации.


    Касательно процедурных макросов: забавно, как возможность делать низкоуровневые вещи открывает языку путь к построению предельно высокоуровневых абстракций. Дело в том, что процедурные макросы в Rust — это своего рода "плагины к компилятору", которые пишутся на самом Rust. Так как Rust — это язык без сборщика мусора, то он может использоваться для создания встраиваемых компонентов. В частности, можно написать динамическую библиотеку, которую подгрузит компилятор при компиляции вашей программы, и которая будет реализовывать ваши собственные расширения языка. Взглянем на пример использования атрибутных процедурных макросов в actix-web:


    use std::io;
    use actix_web::{get, web, App, HttpServer, Responder};
    
    #[get("/{id}/{name}/index.html")]
    async fn index(info: web::Path<(u32, String)>) -> impl Responder {
        format!("Hello {}! id:{}", info.1, info.0)
    }
    
    #[actix_rt::main]
    async fn main() -> io::Result<()> {
        HttpServer::new(|| App::new().service(index))
            .bind("127.0.0.1:8080")?
            .run()
            .await
    }

    Здесь #[get(..)] и #[actix_rt::main] — это пользовательские атрибуты, которые приведут при компиляции к преобразованию элементов, на которые они навешены, в соответствии с заданной программой. Вот во что развернется код выше при компиляции:


    use std::io;
    use actix_web::{get, web, App, HttpServer, Responder};
    
    #[allow(non_camel_case_types, missing_docs)]
    pub struct index;
    
    impl actix_web::dev::HttpServiceFactory for index {
        fn register(self, __config: &mut actix_web::dev::AppService) {
            async fn index(info: web::Path<(u32, String)>) -> impl Responder {
                {
                    let res = ::alloc::fmt::format(::core::fmt::Arguments::new_v1(
                        &["Hello ", "! id:"],
                        &match (&info.1, &info.0) {
                            (arg0, arg1) => [
                                ::core::fmt::ArgumentV1::new(arg0, ::core::fmt::Display::fmt),
                                ::core::fmt::ArgumentV1::new(arg1, ::core::fmt::Display::fmt),
                            ],
                        },
                    ));
                    res
                }
            }
            let __resource = actix_web::Resource::new("/{id}/{name}/index.html")
                .name("index")
                .guard(actix_web::guard::Get())
                .to(index);
            actix_web::dev::HttpServiceFactory::register(__resource, __config)
        }
    }
    
    fn main() -> io::Result<()> {
        actix_rt::System::new("main").block_on(async move {
            {
                HttpServer::new(|| App::new().service(index))
                    .bind("127.0.0.1:8080")?
                    .run()
                    .await
            }
        })
    }

    Функция index превратилась в структуру с реализацией типажа HttpServiceFactory, а в теле функции main появился код по запуску рантайма для работы с асинхронным кодом.


    Другой пример — макрос html библиотеки yew, который позволяет совмещать Rust-код с html-подобным DSL для создания Web-интерфейсов:


    impl Component for Model {
        ...
    
        fn view(&self) -> Html {
            html! {
                <div>
                    <button onclick = self.link.callback(|_| Msg::Click)>{ "Click" }</button>
                </div>
            }
        }
    }

    Здесь макрос позволяет указать разметку в привычном виде, декларативно, на html-подобном языке, с вкраплениями Rust-кода. Похоже на JSX, расширение языка JavaScript. Только Rust изначально обладает средствами для создания подобных расширений, для него они — обычное дело.


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


    В отличие от некоторых высокоуровневых языков (таких как Python), которые служат своего рода "клеем" для низкоуровневых компонентов, написанных на других языках, Rust сам выступает и в роли "клея", и в роли инструмента реализации "склеиваемых" компонентов.


    Бесплатные абстракции


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


    sleep(5);

    И сравните с тем, как то же самое поведение реализуется в Rust:


    use std::{thread, time::Duration};
    
    thread::sleep(Duration::from_secs(5));

    Где вам понятнее, что происходит и где, по-вашему, вероятность ошибиться меньше? Мне кажется, что ответ очевиден.


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


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


    struct Celsius(f64);
    
    struct Fahrenheit(f64);
    
    let a = Celsius(5.);
    let b = Fahrenheit(5.);

    запустить


    Несмотря на то, что оба значения a и b имеют одинаковое числовое представление, они являются объектами разных типов, и поэтому перепутать и подставить одно значение вместо другого не получится. Этот паттерн называется "Новый тип" (New type), и он совершенно бесплатен в использовании. (Подробнее о преимуществах использования паттерна "Новый тип" вы можете прочитать в замечательной статье Передача намерений.)


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


    Обобщенные типы


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


    fn min<T: PartialOrd>(a: T, b: T) -> T {
        if b < a { b } else { a }
    }

    запустить


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


    pub struct HashMap<K, V, S = RandomState> {
        ...
    }
    
    impl<K, V, S> HashMap<K, V, S>
    where
        K: Eq + Hash,
        S: BuildHasher,
    {
        ...
        pub fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V>
        where
            K: Borrow<Q>,
            Q: Hash + Eq,
        {
            self.base.get(k)
        }
        ...
    }

    std::collections::HashMap


    Метод get хеш-таблицы, хранящей значения типа V, соответствующие ключам типа K, принимает ссылку некоторого типа Q, которую также можно получить из ссылки на ключ (это правило задано отношением K: Borrow<Q>). Что это значит? Это значит, что для случая, например, строковых ключей (K = String) вместо того, чтобы всякий раз передавать в get ссылку на владеющую строку (&Q = &K = &String), вы можете, скажем, передавать строковый срез (&Q = &str), к которому можно привести ссылку на владеющую строку с помощью операции Borrow. Это позволяет избежать лишних аллокаций и просто повышает удобство использования: вызывающий код не должен думать, какую именно строку передавать — метод get корректно сработает для обоих вариантов.


    Выглядит как что-то низкоуровневое. Но такое поведение часто реализует определенное требование самой задачи. Например, у нас есть тип с приватным конструктором:


    pub struct UserId(u32);

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


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


    impl Borrow<u32> for UserId {
        fn borrow(&self) -> &u32 {
            &self.0
        }
    }

    запустить


    Вернемся к определению метода get. Для типа Q там представлено также одно весьма низкоуровневое ограничение — Q: ?Sized. Оно говорит о том, что тип Q не обязательно должен быть типом с известным размером на этапе компиляции (что задано по умолчанию для обобщенных типов). Так как в метод передается только ссылка на этот тип, сам тип может быть и типом с неизвестным размером. Размеры типов принимаемых и возвращаемых значений у функций всегда должны быть известны на этапе компиляции, иначе будет невозможно зарезервировать для них место на стеке.


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


    pub trait AsMapKey: ?Sized + Hash + Eq {}
    
    impl<T: ?Sized + Hash + Eq> AsMapKey for T {}

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


    Перечисление типов


    В Rust есть тип-перечисление enum, который в отличии от перечислений в других языках, является перечислением не константных значений одного типа, а перечислением разных типов, которые может принимать значение. Это — алгебраический тип данных (АТД), простой и выразительный инструмент для конструирования высокоуровневых абстракций.


    Хороший пример АТД — это тип Value библиотеки serde_json, который представляет любое корректное JSON-значение:


    pub enum Value {
        Null,
        Bool(bool),
        Number(Number),
        String(String),
        Array(Vec<Value>),
        Object(Map<String, Value>),
    }

    serde_json::Value


    АТД во многих случаях избавляет программиста от написания низкоуровневого кода для проверки целостности и непротиворечивости типов данных. Что актуально не только для языков с динамической типизацией, но и для статически типизированных языков.


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


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


    Вот как решается проблема реализации отсутствующего значения в Rust:


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

    std::option::Option


    С таким подходом можно убрать null из безопасной части языка совсем. Концептуально и просто, теперь сам Option опционален и вписан в существующую систему типов как абстракция более высокого уровня, равно применимая к любым типам, не только к ссылочным. "Под капотом", в реализации, ссылочные типы все равно имеют null, поэтому для таких типов использование Option не влечет накладных расходов: Option::None представляется как null и дополнительного места для хранения дискриминанта не требуется. Так что и для низкоуровневых системных задач Option применим.


    Подробнее об АТД и преимуществах их использования, вы можете прочитать в статье Романа Душкина "Алгебраические типы данных и их использование в программировании".


    Владение


    Концепция владения в Rust постулирует единственность владельца ресурса в любой момент времени. Она вводилась для решения проблемы гонки данных при конкурентном доступе и проблемы использования памяти после освобождения. Однако кроме этого, концепция владения позволила легко реализовать механизм автоматического освобождения ресурсов, где ресурсом может выступать не только память, но также файлы, сокеты и любые другие пользовательские объекты. Если владелец ресурса всегда один, то когда он выходит из области видимости и уничтожается — ресурс автоматически освобождается. Пользователь может задавать собственную процедуру освобождения, реализуя типаж Drop для своего типа.


    В Java, например, с try-with-resources ответственность за корректное освобождение ресурсов перекладывается на вызывающую сторону. К тому же не всегда использование ресурсов настолько локализовано, что безошибочное использование try-with-resources очевидно. Использование Cleaner улучшает ситуацию и избавляет пользователя от необходимости следить за освобождением в тривиальных случаях, но в более сложных — головной боли не избежать (подробнее о проблемах освобождения ресурсов в Java смотрите в лекции Евгения Козлова "Вы все еще используете finalize()? Тогда мы идем к вам").


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


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


    mod machine {
        pub struct StateA(String);
    
        pub struct StateB(String);
    
        pub struct StateC(String);
    
        pub fn start(data: impl Into<String>) -> StateA {
            StateA(data.into())
        }
    
        impl StateA {
            pub fn step(self) -> StateB {
                StateB(self.0)
            }
        }
    
        impl StateB {
            pub fn step(self) -> StateC {
                StateC(self.0)
            }
        }
    }
    
    let a = machine::start("Hello");
    let c = a.step().step();

    запустить


    Вне модуля machine значение типа StateC невозможно получить никаким другим способом, кроме как из значения типа StateA, путем вызова метода step два раза, с переходом через состояние StateB. Причем вызвать step на конкретном значении возможно только один раз.


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


    Заимствование


    На низком уровне заимствование означает получение ссылки на объект, время жизни которой компилятор проверит на соответствие времени жизни исходного объекта. Но при взгляде с более высокого уровня, заимствование означает получение некоего представления (view), временно соотнесенного с исходным объектом. Такое представление не обязано быть единственным.


    Например, для стандартного типа String, благодаря реализации типажа Deref, при использовании операции заимствования мы можем получить одно из двух различных представлений: это ссылку на владеющую строку &String или строковый срез &str. Дополнительно, благодаря реализации типажа AsRef, вызвав у владеющей строки метод as_ref можно получить ее представление как байтовый срез &[u8], как заимствование платформо-специфичной строки &OsStr или как заимствованное представление пути &Path. При этом OsStr можно получить как из String, так и из OsString (для работы с которым он прежде всего предназначен). При этом Path конструируется из OsStr следующим образом:


    pub struct Path {
        inner: OsStr,
    }
    
    impl Path {
        pub fn new<S: AsRef<OsStr> + ?Sized>(s: &S) -> &Path {
            unsafe { &*(s.as_ref() as *const OsStr as *const Path) }
        }
        ...
    }

    std::path::Path source


    Неплохо, правда? То есть, Path::new принимает ссылку на значение любого типа, который представим как &OsStr, и преобразует &OsStr в &Path напрямую, через кастинг указателей, к которым можно преобразовать эти ссылки (это безопасно в связи с идентичным внутренним представлением OsStr и Path). Таким образом, конструктор Path::new не конструирует никакого нового объекта, он просто преобразует заимствование одного типа в заимствование другого типа. String можно преобразовать в Path именно потому, что String заимствуется как OsStr, а он в свою очередь имеет такое же физическое представление, как и Path.


    Существует много полезных типов-заимствований, в Rust они используются повсеместно. Скажем, итератор, пример которого показан в самом начале статьи. Или различные пользовательские DTO-типы, которые принято использовать для записи объектов в базу данных с помощью diesel. Концепция заимствования в Rust — это нечто большее, чем просто работа со ссылкой на конкретный тип, она позволяет на более высоком уровне создавать различные типы-представления, альтернативные интерпретации одного и того же объекта, разные логические стороны которого зависят и ссылаются на данные исходного владеющего типа.


    Что же в итоге?


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


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


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


    Upd.: Отдельное спасибо T_12 за вычитку текста статьи и дельные замечания.

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

    Где вы используете Rust?

    • 6,1%Больше в системной разработке27
    • 25,4%Больше в прикладной разработке112
    • 68,4%Не использую301

    Является ли Rust универсальным языком программирования?

    • 64,1%Да230
    • 35,9%Нет129
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 234

      +13

      Проделал путь java — > c — > python — > rust, и теперь даже скрипты для аналитики пишу на нем. Ибо в программе на питоне не вылезаешь из дебагера, в расте дебагер я не использовал не разу. Как пример, был многоуровневый json, нужно было его его сгрупировать различными способами и вывести по этой группировке статистику.
      В расте это представимо как HashMap<str, StatsStruct >, в питоне же это HashMap<str, Hasmap >.
      На питоне скрипт написал быстро.
      Потом через неделю структура данных немного изменилась и рефакторинг превратился в ад по понятным причинам(ручной поиск и переименование ключей для мап и отсутствие какой либо помощи от интерператора).

        0

        К слову говоря, возможно TypedDict помогли бы в этой ситуации

        –2
        Прикладной, говорите. А как будет выглядеть на Расте такой код 10-летней давности ?
        var
          i, j: Integer;
          Sheets: Variant;
        begin
          XLApplication := CreateOleObject('Excel.Application');
          XLApplication.Visible := True;
          XLApplication.Workbooks.Add;
          XLApplication.Workbooks.Add(xlWBatChart);
          XLApplication.Workbooks.Add(xlWBatWorkSheet);
          XLApplication.Workbooks[2].Sheets.Add(,,1,xlChart);
          XLApplication.Workbooks[3].Sheets.Add(,,1,xlWorkSheet);
          for i := 1 to XLApplication.Workbooks.Count do begin
            ListBox1.Items.Add('Workbook: ' + XLApplication.Workbooks[i].Name);
            for j := 1 to XLApplication.Workbooks[i].Sheets.Count do
              ListBox1.Items.Add('  Sheet: ' +  
                XLApplication.Workbooks[i].Sheets[j].Name);
          end;
        end;

          +8

          Ну, например, так:


          use excel::{Application, WorkbookTemplate, SheetType};
          
          let mut app = Application::new()
              .with_visible(true)
              .with_workbooks([
                  WorkbookTemplate::Empty,
                  WorkbookTemplate::BatChart,
                  WorkbookTemplate::BatWorkSheet,
              ].into());
          app.workbooks[1].sheets.add(SheetType::new_chart().with_count(1));
          app.workbooks[2].sheets.add(SheetType::new_work_sheet().with_count(1));
          
          for workbook in &app.workbooks {
              list_box.items.add(format!("Workbook: {}", workbook.name));
              for sheet in &workbook.sheets {
                  list_box.items.add(format!("  Sheet: {}", sheet.name));
              }
          }
            –1
            Это компилируемый рабочий пример?
              +11

              Мы обсуждаем возможности самого языка или имеющиеся библиотеки? Я вам показал, как выглядел бы код на Rust, который привели вы. Есть ли в экосистеме Раста библиотеки для работы с excel — это другой вопрос, к статье отношения не имеющий.

                –5

                В примере демонстрируется не какая-то библиотека, а динамические возможности Delphi при интеграции ActiveX компонента. Т.е. там дельфи не знает про excel, а знает только про IDispatch.

                  +5

                  И где там эти динамические возможности демонстрируются? Я вижу статическую строку 'Excel.Application', вижу статические обращения типа XLApplication.Workbooks — это все написано в самом коде программы. Где динамика-то?

                    –3

                    Тем что никак не надо описывать нигде отдельно интерфейс Excel компонентов. И этот код можно скомпилировать на машине без установленного Excel.


                    Я не говорю, что этот подход во всем лучше чем Rust. Возможно, где-то есть библиотека, которая содержит макросы, которые реализуют type provider для библиотек типов COM, но это по своим свойствам будет отличаться от динамики — надо будет извлекать типы из excel, где-то их сохранять и так далее.

                      +6

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

                        –3

                        Отсутствие проверок типов это и достоинство и недостаток. Как и почти что угодно.


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


                        существование генераторов прямо сейчас я не проверял

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


                        Например, представьте что вам прямо сейчас надо сделать простой отчет на Excel, сохранить его в PDF и отослать кому-то. Решение будет работать ровно в одной конторе для одного пользователя — бухгалтера Марии Васильевны, что вы выберете?


                        Интересно, что в интервью Андрея Бреслава, автора, Котлин "королю программирования" первый сказал, что какие-то возможности динамической типизации есть везде, причем в собственном стартапе они только сейчас доросли до того, чтобы начать применять typescript — cейчас все на JS.

                          +3

                          Представьте, что вам прямо сейчас нужно сделать утилиту командной строки, которая должна работать в Linux-дистрибутиве, для прикладных целей. Что вы выберете? Delphi? А если не его, то значит ли это, что Delphi — не прикладной язык программирования?

                            +1

                            Не совсем относится к вопросу, но все же.


                            Лично я бы выбрал python запакованный pyoxidizer. Грубо говоря интерпретатор Python в Rust приложение. Это улучшает переносимость и если утилитка выстрелит то постепенно бы переносил части на Rust. Но это не точно, ещё не освоил на должном уровне.

                              0
                              В новых Дельфях уже завезли Linux, даже гуёвый, так что пример не такой уж бьющий в цель))
                                –1
                                Можно и на Дельфи, но есть для этого более удобные языки — Питон, ПХП. Да и на Го и Д это будет делать удобнее, чем на Расте.

                                Потому что они имеют нужные прикладные библиотеки и не напрягают ненужными подробностями.
                      –14
                      Ну тогда это просто грязный пиар — язык без библиотек — это не язык прикладного программирования — и не надо вводить людей в заблуждение.

                      А еще это вранье вдвойне — потому что модель память OLE не натягивается на владение Rust (просто так).
                        0
                        У вас какое-то своё определение прикладного программирования?
                          +3
                          язык без библиотек — это не язык прикладного программирования — и не надо вводить людей в заблуждение

                          Пожалуйста дайте ссылку на то место, где я говорю, что Rust — это язык без библиотек.


                          А еще это вранье вдвойне — потому что модель память OLE не натягивается на владение Rust (просто так).

                          А вам не все равно, OLE-объектом вы оперируете в программе или нет, если результат "натягивается" на то, что вам нужно помимо вашего непосредственного участия? Object Pascal, считайте, повезло, что он имеет такое же представление vtable, как в C++. И что, мы теперь все языки с моделью памяти, отличной от C++, вычеркнем из разряда прикладных?

                            +3

                            Вот вам пример из библиотеки intercom, как можно сделать API в Rust для работы с COM-объектами (они тоже основаны на vtable):


                            Rust COM server:


                            pub use intercom::*;
                            
                            #[com_library(Calculator)]
                            
                            #[com_class(Calculator)]
                            struct Calculator {
                                value: i32
                            }
                            
                            #[com_interface]
                            impl Calculator {
                                pub fn new() -> Self {
                                    Self { value: 0 }
                                }
                            
                                pub fn add(&mut self, value: i32) -> ComResult<i32> {
                                    self.value += value;
                                    Ok(self.value)
                                }
                            }

                            C# COM client:


                            class Program
                            {
                                [STAThread]
                                static void Main( string[] args )
                                {
                                    var calc = new Calculator.Interop.Calculator();
                                    calc.Add( 10 );
                                    var result = calc.Add( 100 );
                                    Console.WriteLine( result );
                                }
                            }

                            Вся магия с указателями и прочим скрыта за атрибутными процедурными макросами.

                              0

                              А, наоборот — клиента на Раст, который работает с чем-то через COM? Поддержка использования библиотек типов и так далее?

                                +1

                                Теоретически реально, крейт winapi достаточно хорош и стабилен. Другое дело, что все это обмазано unsafe, как цепь дедового велосипеда солидолом, что может смущать.

                                  +1

                                  … который просто нужно скрыть за высокоуровневым безопасным API.

                                    0

                                    По факту никто этим заниматься не будет — сам по себе winapi интересен только низкоуровенщикам. Максимум — гуеписцам.

                                  0

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


                                  На вскидку, я думаю, что внешний API может быть представлен в виде атрибута, который вешается на статически определенную структуру. А если вам требуется динамика и на момент компиляции вы точно не знаете структуру данных — то дополнительно можно сделать API для работы с такими данными на основе АТД. Так сделано в библиотеке serde_json: вы можете распарсить JSON из строки в АТД Value, либо в собственную структуру, если формат JSON-объекта известен заранее.


                                  Обратите внимание, что в исходном примере на Delphi динамика скорее мешает — она никак по-сути не используется, все записано статически, а контроля типов при этом нет.

                                    0
                                    Обратите внимание, что в исходном примере на Delphi динамика скорее мешает

                                    По сравнению с гипотетической отсутствующей системой, которая включает генерацию типизированной обертки по TLB, да. Но реально этой системы не существуют, возможно, потому, что ее сложнее делать чем просто вызвать метод IDispatch.Invoke пор строчке. При этом система на основе TLB будет работать только с теми приложениями, которые предоставляют наружу TLB а не всеми для которых работает OLE automation.

                                      +4

                                      Думаю, скорее ее нет потому, что она пока особо никому и не нужна.

                                        0

                                        Возможно, потому, что Растом пока не пользуются уж очень сильно как совсем прикладным языком?
                                        Для подобных задач достоинства Раста менее важны чемегонедостатки.

                                          0

                                          Им пока и как системным не очень сильно пользуются.

                                            0
                                            А может стоит сделать по другому? — написать новую статью в каких областях Раст силен, и чем именно, вместо общих фраз и лозунгов.
                                            Навскидку area, для примера
                                            • embedded/iot
                                            • app's
                                            • web server
                                            • web client(wasm)
                                            • coresys/libs
                                            • scripting
                                            • gamedev
                                            • math/gpu/distributed/AI
                                              0

                                              Силен сам Rust или в каких областях больше развита его экосистема?

                                                0
                                                Голый язык мало кому нужен, конечно экосистема.

                                                Забыл еще область программирования для мобилок — andriod/ios
                                                  +3

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


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


                                                  Вам это не нравится? Что же, поклонником Rust вас не назвать, скорее — противником. А если ты делаешь что-то, что не нравится твоему противнику, то это значит, что ты поступаешь правильно. Вот о чем мне говорят ваши комментарии.

                                                    0
                                                    У меня нет возражений по первым двум абзацам.
                                                    Но вот то, что молоток — универсальный инструмент, еще не делает его пригодным для строительства небоскреба. Разночтение в понимании «универсальный ЯП».

                                                    По поводу третьего — так что, моя идея показать силу Раста в конкретных применениях отклоняется из желания насолить «противнику»? =)

                                                    Кстати, я нашел общее, что мне не нравится в статьях растаманов — упоминаются исключительно только Pro-, но никак не Contra- аргументы. Вот и помогаю составить целостную картину в меру своих ограниченных способностей.
                                                      +2

                                                      Напишите статью с контр-аргументами, с удовольствием почитаю.

                                                        0
                                                        Чего то подобное есть в планах, но раста касается опосредованно -только как одного из многих, и нескоро.

                                                        Не вижу смысла писать чисто негатив — это точно также искажает картину. В конце-концов Раст дал живительного пендаля целой индустрии, а это чего то да стоит!
                                                +1

                                                Надо просто сделать State of the Rust ecosystem по аналогии с этим (хотя я по последней ссылке не со всем согласен, например, numerical programming вполне ок, да и machine learning я довольно продуктивно делал, ну да ладно).

                                                  +1
                                                    +1
                                                    Это какой-то новомодный тренд заводить десятки похожих доменов вместо заведения одной wiki? После wiki-статьи «State of the Haskell ecosystem», набор ссылок «areweyet чего-то там» оставляет впечатление какой-то маркетологической фигни.
                                                    Кстати, для ссылки arewestableyet даже валидный сертификат не подвезли.
                                                      0

                                                      Это уже не ко мне вопрос. Видимо, тренд :dunno:

                                                        0

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


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

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

                                                        Не новомодный — это старая полустебная традиция из Мозиллы.


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

                                            0

                                            По личному опыту — то ли TLB, то ли в дельфишном генераторе модулей из TLB встречаются ошибки, из-за которых полностью статически написанное взаимодействие с Excel/Word не всегда работает. И время от времени приходилось писать что-то типа
                                            Variant(workBook). Save(reWrite := xlRewrite). Конкретных функций и параметров уже не помню, но иногда без этого просто не работало.


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

                                              0

                                              IDispatch только

                                        –2
                                        Я привел работающий пример. Нужен Эксель, а не Калькулятор.

                                        Если для этого и подобного мне нужно каждый раз писать собственную [системную] библиотеку, этот язык не годится для прикладного программирования. Batteries Not Included

                                        Эксель тут только для примера — для прикладного программирования нужен нормальный фреймворк промышленного качества, поддерживаемый, с сетевыми функциями, драйверами к СУБД, ГУИ, функционально полным Веб — фреймворком. Пока этого нет, говорить рано.
                                          +3

                                          А как вы думаете, сколько усилий было вбухано в эти самые драйвера СУБД, ГУИ, веб-фреймворки на С++? Вы хотите, чтобы это всё резко появилось в языке, в который усилий вложено порядка эдак на три меньше?


                                          Кстати, как там дела с подобным dynamic binding в С++?

                                            –2
                                            Я хочу, что бы не было попыток ложной рекламы.

                                            В С++ тоже весьма неудобно, хотя OLE там родное. Но с помощью MFC (либо С++Builder'a) и какой то матери, жить можно «из коробки». А почему Вы меня спрашиваете про С++?
                                              +3

                                              OLE там родное, потому что оно изначально под C++ и делалось.


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

                                                +2
                                                В С++ тоже весьма неудобно, хотя OLE там родное. Но с помощью MFC (либо С++Builder'a) и какой то матери, жить можно «из коробки».

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


                                                А почему Вы меня спрашиваете про С++?

                                                Признаю, тут я ненамеренно переключил контекст. Прошу прощения.


                                                Впрочем, если касаться Delphi/Object Pascal, конкретно эту фичу скорее всего проектировали конкретно под поддержку COM/OLE. С моей точки зрения, отсутствие/наличие какой-то одной подобной фичи не делает язык специализированным/общего назначения. Сможете ли Вы так сделать "из коробки", зависит от того, написал ли кто-то ранее нужную библиотеку. Сможете ли вы так сделать удобно и быстро "из коробки", зависит от того, посчитали ли авторы языка эту фичу достаточно важной для встраивания в ядро.


                                                Как контр-пример — задача работы с 3D Boundary Representation (BRep) данными. Есть ли для Delphi/Object Pascal подобные вещи "из коробки"? Сомневаюсь. Есть ли подобные библиотеки в принципе? Может быть, не знаю. Делает ли это Delphi хуже? Нет. Делает ли это Delphi в принципе неприменимым для задачи? Нет.

                                                  –3
                                                  Как контр-пример — задача работы с 3D Boundary Representation (BRep) данными. Есть ли для Delphi/Object Pascal подобные вещи «из коробки»? Сомневаюсь. Есть ли подобные библиотеки в принципе? Может быть, не знаю. Делает ли это Delphi хуже? Нет. Делает ли это Delphi в принципе неприменимым для задачи? Нет.
                                                  Ну это уже совсем редкий случай (в отличие от Экселя), я бы взял для примера какие нибудь мат.библиотеки.
                                                  И мой ответ был бы таким — Дельфи тут не применяем, для этой задачи берем язык с адекватным набором либ.
                                                    +4

                                                    А вот для меня как раз пример с Экселем видится мелким и редким. Потому что мои программы должны уметь формировать документы MS Office даже если на компьютере не установлен этот самый MS Office и к тому же нет активного сеанса пользователя.

                                                      0
                                                      Ну это уже совсем редкий случай (в отличие от Экселя)

                                                      Ну как вам сказать… AutoCAD, SolidWorks, Siemens NX, CATIA, Creo… Это первые пришедшие в голову крупные CAD/CAE пакеты. Я с экселем за последние лет 12 столкнулся один раз, с его COM API — ни разу. А с CAD'ами последние 5 лет имею дело. Так что тут забавный вопрос, какая область применения "ширше".


                                                      И мой ответ был бы таким — Дельфи тут не применяем, для этой задачи берем язык с адекватным набором либ.

                                                      Значит дельфи тоже не прикладной? Или всё-таки прикладной?

                                                        –1
                                                        Надо разделять OLE Automation и работу с форматами данных. Пока что обсуждалось первое, и проблема не поменяется от замены слова Эксель на Автокад.

                                                        Значит дельфи тоже не прикладной? Или всё-таки прикладной?
                                                        К другой области прикладной =)
                                                +2
                                                Если для этого и подобного мне нужно каждый раз писать собственную [системную] библиотеку, этот язык не годится для прикладного программирования.

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

                                                  0

                                                  Я пока только лишь изучаю Rust, но во-первых, для Rust есть и драйвера к СУБД, и ГУИ и даже веб-фреймворк (наверняка даже не один). Кроме того, в нем также можно задействовать существующие C и C++ библиотеки (придется делать дополнительные движения, конечно, но многие популярные уже обернуты и доступны на crates.io).


                                                  Кроме того, я пишу уже более 20 лет на Python (где-то года с 1999). Сейчас не осталось наверное почти ни одной библиотеки или фреймворка, из тех, что я использовал в 1999. Те же драйвера для СУБД для Python переписывались неоднократно, некоторые с нуля, притом. То же самое справедливо почти для любого другого языка.


                                                  В общем, в силу того, что я пока только прочитал ⅔ Rust Book, и еще не написал ни одного полноценного приложения на Rust, не могу полноценно сравнивать его ни с чем, но пока что он мне нравится.


                                                  До Python я еще делал попытки писать на C++, и это было сложно, на мой взгляд. А сейчас, когда смотрю на современный C++ — ну это просто какой-то дикий комбайн, для управления которым надо получить PhD и еще 10 лет опыт нарабатывать.


                                                  Rust в сравнении нравится тем, что в нём все просто и логично. C++ при всем желании простым и логичным не назовёшь.

                                                    0
                                                    В общем, в силу того, что я пока только прочитал ⅔ Rust Book, и еще не написал ни одного полноценного приложения на Rust, не могу полноценно сравнивать его ни с чем, но пока что он мне нравится.
                                                    Прекрасно — буду рад увидеть от Вас статью типа «как я переписывал свое приложение с Питона на Раст».

                                                    Когда я вижу библиотеку версии — 0.3, например для MSSQL, я сомневаюсь тащить это в прод.
                                                      0
                                                      Когда я вижу библиотеку версии — 0.3, например для MSSQL, я сомневаюсь тащить это в прод.

                                                      Про zerover слышали? Говорят, сейчас модно, а в Rust особенно. :)
                                                      https://0ver.org/

                                                        0
                                                        ОМГ, молодежно, буду знать. И РеактОС в этом списке яркий пример небеты!
                                                        +1
                                                        Еще неизвестно, что хуже — 0.3 или 152.3.2 как форсит Хромиум, за которым подтягивается и Мозилла. Когда три внутренних багфикса наращивают мажорную версию — это, на мой взгляд, перебор.
                                          +4

                                          Именно этот — дословно так же. Даже чище, поскольку не нужно объявлять все вспомогательные переменные сразу и пользоваться for-циклом для итерации.


                                          Упд: собственно, ответ топикстартера выше демонстрирует.


                                          Отдельное спасибо НЛО за редактирование в мобильной версии сайта!

                                            0

                                            Примерно так и будет, см. https://github.com/informationsea/xlsxwriter-rs

                                              0

                                              Как я понял, библиотека специализированная на работе с xlsx-файлами без экзеля? Тогда это совсем не то. Код на Delphi может также показать файлик пользователю, обработав при этом попытку закрытия файла, потом, не закрывая excel-я, считать всё, что пользователь там понавводил.
                                              А по ссылке — просто библиотека для создания/редактирования одного формата. Для какого-нибудь веб-сервиса, которому надо отчетик сгенерить без графиков — это то, что нужно, но для приложения, которому нужны все возможности excel-я этого будет мало.


                                              Плюс, тому, кто привык к ole-интерфейсу экзеля, названия функций будут сильно непривычными.

                                                0
                                                это не OLE Automation
                                                +6
                                                А что у вас за проблемы с растом, господин симаргл?
                                                Вы приходите в каждый тред про раст, пишите довольно странные вещи.
                                                Вам каждый раз терпеливо аргументируют, вас минусуют, по существу. Но вы продолжаете по новой.
                                                Без наезда, просто интересно, что заставляет человека так воинствующе пытаться очернить то, в чем вы не шарите.
                                                Тут ведь явно какие-то личные мотивы.
                                                  –6
                                                  Не люблю ложную и навязчивую рекламу.
                                                  В данном топике это, что раст подходит всем.
                                                    +3
                                                    Ну вы искажаете суть. Как древняя шутка про джаву:
                                                    «Saying that Java is nice because it works on all OS's is like saying that anal sex is nice because it works on all genders»

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

                                                    Когда такое делает Полухин это понятно. Для него это job security, и удовлетворение потребности в признании. Он признаваем в мире плюсов и боится, что его этого лишат.
                                                    Но вам то какая разница? А если разница и есть, то аргументы нужны хотя бы по существу.
                                                    У Антона они кончились и он замолк, а вы то зачем продолжаете?
                                                      –1
                                                      Вы используете не хорошие приемы, для отстаивания своей точки зрения.
                                                      Отвечаете на не существующие тезисы. Ведете всякие демагогические приемы.
                                                      Конкретнее, пжлста.
                                                      Я привел пример прикладной программы, умеющей в OLE Automation, это демагогия?
                                                        +3
                                                        Ну тогда это просто грязный пиар — язык без библиотек — это не язык прикладного программирования — и не надо вводить людей в заблуждение.


                                                        Вы всегда, на любом языке/инфраструктуре, сможете найти либу, которой нет для какой-то вашей экзотической задачи.
                                                        Что это доказывает? Это и есть самое настоящее искажение. Либы пишут компании за деньги. Если вы считаете, что для чего-то нет апи на таком-то языке, то это не язык вас обманул, а человек, который не предоставил апи, ибо, умер\нет времени\нет денег\ нет желания.

                                                        Ну и потом. Здесь речь не про конкретно эту ветку. Я смотрю по вашим комментариям, они же все такие. Вы врываетесь в каждый тред, пишете какую-то дичь. Вам не смотря на это отвечают по существу и аргументировано. У вас конечно аргументов не остается и вы идете «гадить» дальше. Такое возможно только за зарплату или по личной глубокой обиде/страху.

                                                        Вы иногда задаете и правильные вопросы. Но даже в таком случае, тон все-равно остается очень «с наездом».
                                                          –1
                                                          Ну без конкретики на эмоции я отвечать не буду. Где то я бываю неправ — тогда я это признаю.
                                                            –1

                                                            Вашу бы энергию, да в мирное русло )

                                                            +4

                                                            Этот человек — из "группы поддержки" языка D, как я понял. Так что да, Rust для D все-таки конкурент и у D дела пока идут хуже, чем у Rust.

                                                              –1
                                                              Отличная стратегия поддерживать свой любимый язык, пытаясь обливать помоями другой язык. Типичный подход неудачников.
                                                              Лучше бы сделал что-то стоящее на D, раз такая любовь.
                                                              Но как известно, трындеть — не мешки ворочать.
                                                                0
                                                                Критическая статья про D в черновике.

                                                                Какие то любители крайностей, право слово.
                                                                  0

                                                                  Только не очень понятно про какой язык вы в первом предложении.

                                                    –4
                                                    Частенько вижу новости по расту и уже несколько лет пытаюсь понять почему он существует.
                                                    Признаюсь, я не пытался его изучать как раз по этой же причине (потому что мне дорого мое время и я могу его потратить на экосистему в которой я уверен).
                                                    Насколько я понимаю мозилла предлагает его как безопасная замена C++, но синтаксис (и это не только я заметил) крайне отвратный (в том числе в этой статье), даже по сравнению с плюсами. Судя по докладу от уважаемого мной, но предвзятого человека (Антон Полухин известный адепт C++), www.youtube.com/watch?v=fT3OALUyuhs каких-то новшеств в безопасности от rust'а ждать не стоит. Кроме borrow checker'а, который судя по докладу не работает и непонятно когда заработает. «Safe mode» в rust как я понимаю просто является встроенным в язык статическим анализатором кода не особо отличающийся(?) от таких же анализаторов на с++. Поэтому я совершенно не понимаю зачем он вообще нужен и почему бы не улучшать тот же с++, вместо того чтобы плодить еще одну экосистему со своими проблемами.
                                                    Может я чего-то не понимаю и пропускаю что-то важное? Буду рад если вы меня переубедите.
                                                      +10

                                                      Вероятно вы пропустили мою статью.

                                                        +3
                                                        Во-первых прочитайте статью (и комментарии к ней) от humbug — станет яснее.

                                                        Borrow checker в Rust отлично работает, зря вы так.

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

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

                                                        Какой путь больше подходит — решать Вам.
                                                          +7
                                                          Проблема в том, что C++ ушёл настолько далеко вперёд, что у новичков уже нет шансов его полноценно освоить. Да, лет за пять можно более-менее загрузить в мозг текущее состояние языка и связанной с ним экосистемы. Но это будет лишь текущий статический срез, который не даст представление о best practices и общепринятых способах решения проблем.
                                                          По-настоящему язык знают лишь те, кто развивался вместе с ним. Кто изучал C++ в динамике, по мере его формирования — читая рассылки и форумные дискуссии, и даже участвуя в них. Сталкиваясь с проблемами — и наблюдая, как в стандарт и в экосистему приходят новые способы их решения. Только тогда человек точно знает: как, почему и для чего появилась та или иная возможность или особенность стандарта.
                                                          Ни одна книга не даст этого опыта. Этот поезд уже ушёл, и максимум, на что можно рассчитывать — зацепиться за последний вагон, чтобы потом ехать снаружи.
                                                          С другой стороны, Rust только начинает развиваться. И у новичков, которые зайдут в этот поезд прямо сейчас, есть уникальная возможность развиваться вместе с ним, отслеживая все нововведения и разворачивающиеся вокруг дискуссии.
                                                            0
                                                            Но это будет лишь текущий статический срез, который не даст представление о best practices и общепринятых способах решения проблем.

                                                            Так если мы пишем кодовую базу с нуля, то нам как раз таки и нужен статический срез свежака без всего это легаси. Нам и нужен именно тот самый Modern C++. И нет нужды в знании старого С++. Вам нужно знание более старого С++ только если вы работаете с кодовой базой, которая также на нём написана. Я не вижу смысла для себя в 2020 знать, как там проблемы решались в С++98 (хоть и знаю, так как раньше работал с таким кодом, но теперь я с ним не сталкиваюсь).
                                                            Только тогда человек точно знает: как, почему и для чего появилась та или иная возможность или особенность стандарта.

                                                            Я считаю такие знания бесполезными для пользователей. Он не должен думать о причинах, он должен знать средства для решения проблем, не более того. Причины, историю и так далее оставим ребятам в Комитете, это их хлеб.
                                                            Ни одна книга не даст этого опыта. Этот поезд уже ушёл, и максимум, на что можно рассчитывать — зацепиться за последний вагон, чтобы потом ехать снаружи.

                                                            Не вижу в этом ничего плохого. Если работаете только с новой кодовой базой — то почему бы и нет? Если приходится и старое трогать (а в любом развивающемся языке будет старая и новая кодовая база — это неизбежно), то по ходу дела можно и подучить. Тут ничего уникального у С++ нет.
                                                            С другой стороны, Rust только начинает развиваться. И у новичков, которые зайдут в этот поезд прямо сейчас, есть уникальная возможность развиваться вместе с ним, отслеживая все нововведения и разворачивающиеся вокруг дискуссии.

                                                            … И через парочку ревизий языка им придётся знать несколько Editions. То есть кардинально ситуация не поменялась :) Я не считаю, что пользователи должны за всем этим следить. Просто должны проверять к каждому Edition штуки, какие завезли, думать об апгрейде компилятора, если это возможно в их ситуации, обдумывать, как применять новые фишки. Зачем читать все эти дискуссии и так далее — без понятия.
                                                              0
                                                              Так если мы пишем кодовую базу с нуля, то нам как раз таки и нужен статический срез свежака без всего это легаси. Нам и нужен именно тот самый Modern C++.

                                                              А есть спецификация этого Modern С++? Ну понятно, что умные указатели надо использовать, а может чего ещё? Сдается мне, что даже в комитете не понимают текущий рекомендованный срез. Я этот вопрос уже задавал к статье о фишка C++ 2020 и как-то хорошего ответа не нашлось.

                                                                0
                                                                Ответа размером с книгу? И как умные указатели помогут мне в реализации IPC shared memory? А в качестве указателей на регистры (в микроконтроллерах)? Я имею в виду что обычные указатели никуда не делись, и для них найдется применение. :)
                                                                  +1
                                                                  Ответа размером с книгу?


                                                                  Я думаю, acmnu как раз скажет вам спасибо, если вы ему подскажите в какой книге хорошо описано что и как сейчас стоит использовать, а чего стоит избегать.
                                                                    0

                                                                    Ну да, хорошей книги бы хватило.

                                                                    0
                                                                    А есть спецификация этого Modern С++?
                                                                    C++ Core Guidelines
                                                                      0

                                                                      Да, видел уже эту ссылку. Жалко новичкам она не подойдет, но мне сгодится. Спасибо.

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

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

                                                                      0

                                                                      Становится чуть менее больно жрать кактус?

                                                                    +5

                                                                    Могу ошибаться, но мне видится, что у Rust немного другой путь.


                                                                    Если C++ изначально был достаточно целостен, а потом в него сверху накручивались дополнительные фичи (грубо говоря), то Rust изначально имеет стройную концепцию, которая реализована не полностью. Львиная доля сложности языка как раз состоит в понимании исключений и частных ограничений языка, которые сама концепция, кажется, иметь не должна. Отсутствие const generics (скоро будут), отсутствие специализации, отсутствие NLL (уже реализовано), нет HKT ни для типов, ни для лайфтаймов и так далее. У языка очень большая перспектива для развития именно по пути к своему внутреннему концепту, пока реализованному лишь отчасти.


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


                                                                    Также в Rust-компиляторе разрабатывается слой MIR (mid-level intermediate representation), который в будущем позволит не только менять бекэнд (сейчас используется LLVM), но и "фронтэнд", могут появиться новыя языки, которые будут компилироваться в Rust MIR.

                                                                      +2
                                                                      Если C++ изначально был достаточно целостен
                                                                      Только для мало знакомых с историей. Сначала назывался «Си с классами». Прикрутили проволокой ООП к Си.
                                                                      До этого был Си как развитый ассемблер. Примотали синей изолентой структурное программирование.
                                                                      C/C++ это как раз стихийно созданное нечто, захватившее много пространства в ПО. Хотя были и есть более правильно устроенные языки, созданные архитекторами ПО, но…
                                                                      Надеюсь, что Ржа создана хорошо. Но если это так, то, как показывает опыт, применение языка может быть ограничено.
                                                                        0
                                                                        Прикрутили проволокой ООП к Си.

                                                                        Ну, в этом же и была его концепция, если кратко :)

                                                                        0
                                                                        Rust изначально имеет стройную концепцию, которая реализована не полностью. Львиная доля сложности языка как раз состоит в понимании исключений и частных ограничений языка, которые сама концепция, кажется, иметь не должна. Отсутствие const generics (скоро будут), отсутствие специализации, отсутствие NLL (уже реализовано), нет HKT ни для типов, ни для лайфтаймов и так далее

                                                                        А что можно почитать подробнее про эти стройные концепции?

                                                                          0

                                                                          Можно посмотреть на Haskell и представить, как это все может заработать в императивном языке, заточенном под эффективность и без сборщика мусора )


                                                                          Если серьезно, то одного единственного документа, наверное, нет. Но кое-что вырисовывается из набора RFC: https://github.com/rust-lang/rfcs/tree/master/text

                                                                            0

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


                                                                            Например, есть критика Rust за отсутствие HKT, монад и do-нотации, что приводит к частным решениям (например, тут. Насколько я понимаю, HKT в Rust пока что не планируются, планируются GAT, которые (как говорит уважаемый 0xd34df00d) не являются полноценной заменой (увы, здесь моих знаний не хватает). Вот разработчик говорит, что do-нотация не очень совместима с императивной парадигмой. (Оффтоп: и зачем им return, break итп внутри do-блока?)

                                                                              +2

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

                                                                                0
                                                                                (Оффтоп: и зачем им return, break итп внутри do-блока?)

                                                                                Хаскелистам? return — это на самом деле аппликативный pure, «запихнуть готовое чистое значение в монаду». Это даже не ключевое слово, а обычная функция (пусть и являющаяся частью соответствующего тайпкласса). А break вообще разбивает список на две части по предикату, к циклам и управлению потоком выполнения он отношения не имеет.

                                                                                  0
                                                                                  Хаскелистам?
                                                                                  Да нет, растаманам разработчикам Rust из по ссылке из моего сообщения, в которой говорится, что do-нотация не ложится на императивную парадигму, в которой присутствует управление потоком выполнения (return, break, continue)
                                                                                    0

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

                                                                                0

                                                                                Хаскель, кстати, так себе с точки зрения цельности и стройности.


                                                                                А ещё можно посмотреть на ATS (но я не смотрел особо плотно).

                                                                          +2
                                                                          Забыл добавить. По крайней мере на моём опыте я совершенно недавно начал писать абсолютно новую кодовую базу (то есть легаси ровным счётом ноль). Но как «нативный» ЯП я выбрал именно С++. Не потому, что я его лучше знаю (более чем уверен, что мне хватило бы 2-3 недель, чтобы на него пересесть более-менее нормально), а потому, что не нашёл нужных мне библиотек (там нет аналога SObjectizer) и просто потому, что Rust не решает проблем лучше С++, с которыми я сталкиваюсь в разработке. Повторюсь, именно те проблемы, с которыми сталкиваюсь я (у вас возможно и будет решать). Но у меня современный С++17 (я бы на С++20 переключился, но не считаю его достаточно стабильным), у меня CMake, Conan, статический анализ (который в том числе и ловит (хоть пока что и не все, как Rust) lifetime ошибки (но у меня пока что ни одной не поймал, потому что ни одной и не было. Не факт, что так будет у вас :)). В целом у меня достаточно современный стек, чтобы на него жаловаться.

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

                                                                            А можно ещё раз попросить Вас дать (ссылку на) пример конфигурации открытых линтеров и прочих инструментов для C++, которые Вы считаете достаточными для нового проекта на современном C++?

                                                                            +2
                                                                            > Это действительно хороший вопрос. Многие считают, что С++ уже не исправить. Лично я так не считаю и своим развитием С++ показывает, что он умеет эволюционировать. Смысл в том, что C++ сейчас как раз и движется в сторону многих вещей, которые Rust уже предлагает.

                                                                            Прям интересно стало. Как вы предполагаете встроить borrow checker в С++? Потому что без этого либо производительность, либо безопасность будут страдать.
                                                                              0

                                                                              Думаю, примерно как тут: https://habr.com/ru/post/501808/

                                                                                0
                                                                                Т. е. вы предлагаете создать отдельное подмножество языка со своей семантикой несовместимой со всем остальным языком, которое будет гордо называться, например, C++ SAFE. Для которого будет своя стандартная библиотека, нужно будет писать враперы (склеивающий код) с существующими библиотеками на C++. И в чем смысл сего действа тогда? NIH синдром?

                                                                                Может есть какие-то более практичные варианты?
                                                                                  –1
                                                                                  Ахах, конечно есть
                                                                                  unsafe {
                                                                                  use std:: 
                                                                                  }

                                                                                  А если почитать ссылку, то для D —
                                                                                  Новый синтаксис не добавляется. В сгенерированный код не вносится никаких изменений.
                                                                                    +2
                                                                                    Да, да! Нового синтаксиса не добавляется а live функции что-такое? А в C++ они тоже есть? Если вы не заметили то я про C++ спрашивал, а не про D.
                                                                                    И по вашей ссылке
                                                                                    > Если функции @ live, вызывают функции, не являющиеся @ live, ожидается, что эти вызываемые функции будут представлять собой @ live-совместимый интерфейс, хотя он не проверяется
                                                                                    Предполагаем что все хорошо, но не проверяем. Как-то попахивает текущим плюсовым походом и безопасность стремительно улетучивается.

                                                                                    > Ахах, конечно есть
                                                                                    Без предоставления стандартной библилотеки, удовлетворяющей правилой новой семантики у вас будет куча кода unsafe (почти весь) и чего вы добьетесь в этом случае? Правильно, ничего.
                                                                                      –5
                                                                                      @ live — это всего лишь атрибут, а механизм атрибутов в С++ тоже есть, даже стандартизовали.

                                                                                      Про «ахах конечно есть» это был намёк про Раст, библиотеки которого сплошь ансейф, если Вы не поняли.

                                                                                      Так что имеем картину — А если он миленький, то какая разница? (с) =)
                                                                                        +2

                                                                                        Тут ниже уже приводили пример, как эти самые "просто атрибуты" выглядят в C++. И, глядя на этот пример, что-то Rust резко ещё более простым и понятным стал :-)

                                                                                          0
                                                                                          @ live — это всего лишь атрибут, а механизм атрибутов в С++ тоже есть, даже стандартизовали.


                                                                                          Получается атрибут live, по сути, берет и превращает текущий C++ в другой язык с другой семантикой (аля С++ SAFE)
                                                                                          И что это меняет относительно моей изначальной посылки? Текущий код и код существующих С++ библиотек не станет автоматически совместим с семантикой нового языка, и нужна будет обернутая/новая стандартная библиотека и враперы для текущих библиотек нужно будет писать или сами библиотеки переписывать.

                                                                                          Про «ахах конечно есть» это был намёк про Раст, библиотеки которого сплошь ансейф, если Вы не поняли.


                                                                                          Не стоит преувеличивать. Как правило это либо биндинги к текущим плюсовым библиотекам (но там особо выбора нет как и скорее всего у условного C++ SAFE), либо unsafe сильно локализован, как правило отдельными небольшими функциями.
                                                                                            –1
                                                                                            Получается атрибут live, по сути, берет и превращает текущий C++ в другой язык с другой семантикой (аля С++ SAFE)
                                                                                            Совсем нет.
                                                                                            В D он включает DFA для конкретных функций. А в C++ пошли другим путем — DFA для всего включается при ключе компиляции.

                                                                                            Не вижу смысла обсуждать с тобой хоть что то, ибо ты не читал базисов, о чем споришь.
                                                                                              +1
                                                                                              А мне кажется, вы пытаетесь сделать хорошую мину при плохой игре, ничего не говоря по сути.

                                                                                              В D он включает DFA для конкретных функций. А в C++ пошли другим путем — DFA для всего включается при ключе компиляции.

                                                                                              Не особо важно на каком уровне включается механизм проверки. Важно как осущесвтляется переход от safe части к unsafe и обратно. И тут, по сути, я вижу только 2 подхода:
                                                                                              1. Перенос классического статического анализатора в компилятор. Который, легко заиспользовать. но он ничего не гарантирует
                                                                                              2. Разметка монструозными атрибутами кода (уж лучше бы новый синтаксис предлагали) с написанием оберток существующих библиотек (в том числе и стандартной библиотеке)

                                                                                              Не такое уж и громоздкое описание подходов получилось.

                                                                                              Приводить в пример D вообще, странно. Т. к. переход к borrow checker'у c языка с GC совсем не тоже самое что переход с языка с ручным управлением памятью.
                                                                                                0

                                                                                                GC в D — опциональная штука.

                                                                                                  +3
                                                                                                  Да, ладно! И стандартная библиотека в D может без GC может работать? И утечки памяти не будет, если его отключить и не включать GC?
                                                                                                    –1
                                                                                                    Фобос — нет (сильно ограниченно), а вот BetterC — да.
                                                                                                    Кроме того, возвращаясь к Расту — режим no_std (BetterC) — предпочтителен для эмбеддед и для риалтайма.

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

                                                                                                      Какое отношение ваши пассажи имеют к сложности прикручивания OB к языку? Собственно, прикручивать что-то такое к языку который поддерживает разные способы управления памятью, существенно сложнее, чем к языку, построенному лишь на одной единственной модели, какая бы она ни была.


                                                                                                      Вот ещё ссылка по теме: https://habr.com/ru/post/460671/

                                                                                                    –1
                                                                                                    Получается, сам спросил, сам и ответил =)
                                                                                                    О чем спор — решительно непонятно, извиняюсь за резкость.
                                                                                                    Оба варианта совмещаются.

                                                                                                    Пока я вижу только 3 системы с DFA — C++, D, Rust. Они и обсуждаются. Возможно, скоро к ним присоединится Nim.
                                                                                                      0
                                                                                                      Мне было интересно «вдруг умные мужики придумали рабочий вариант внедрения borrow checker'a». Но пока что для меня оба эти подхода выглядят очень сомнительно. Не верится мне что они будут работать, что ими будут пользоваться.
                                                                                                        –4
                                                                                                        Кстати, другие языки никогда не получат такой же контроль, как в Расте — не стоит и надеяться.

                                                                                                        Ведь ОВ система защищает вас от удара молотком по пальцам самым простым и надежным способом — просто отрежьте вторую руку, и работайте одной =)
                                                                                  +5
                                                                                  Как новчику начать писать на C++?(эт если че не сарказм, вдруг решу попробовать) Для старта с растом, ты открываешь растбук, она довольно короткая и неплохо так покрывает примитивные типы данных, методы работы и тд и тп. С C++, заходишь на хабр и видишь статью «17 способов инициализировать переменную». Если ты стоишь перед выбором что учить, то учить C++ желания не возникает.
                                                                                  Насколько я понимаю мозилла предлагает его как безопасная замена C++

                                                                                  Мозила может и предлагает, но совершенно очевидно, что кода на популярных языках написано столько, что уйдут они не скоро. И переписывать весь код C++ на раст, конечно никто не будет, из-за мыслей мозиллы. Я любой язык рассматриваю не как замену другому языку, а как язык на котором можно решить ту или иную задачу, и что он меняет, C++, python, go не суть важно.
                                                                                    –2
                                                                                    Как новчику начать писать на C++

                                                                                    Взять любой обучающий С++ ресурс и начать писать. Это может быть книга, видеокурс, whatever. К сожалению, у С++ нет своего бесплатного аналога Rustbook пока что (это вопрос для SG20, который должен решить это, имхо), но есть много уже готовых книг\курсов. Выбор тут явно есть. Ну а по статьям на хабре о 17 способах инициализации судить — это хохма какая-то :)
                                                                                    Если ты стоишь перед выбором что учить, то учить C++ желания не возникает

                                                                                    Это называется маркетинг. И тут Rust действительно показал, как нужно работать на данном поле. И этому у Rust стоит поучиться многим.
                                                                                      +2
                                                                                      Это называется маркетинг

                                                                                      Скорее антимаркетинг, не зря про C++ появились шутки в стиле, «что бы выучить C++ за 24 часа, нужно выучить квант физику, вернуться назад на несколько лет, и выучить с++ к моменту настоящее+24 часа», тоже rust маркетологи придумали? А когда C++'у предпочитают java,c# etc, тоже маркетинг раста?
                                                                                      Взять любой обучающий С++ ресурс и начать писать.

                                                                                      Ну а по статьям на хабре о 17 способах инициализации судить — это хохма какая-то :)

                                                                                      — Сирота?
                                                                                      — Сирота :(
                                                                                      — Мама папа есть?
                                                                                      — Мама папа есть=)
                                                                                    +6

                                                                                    Синтаксис у Раста в целом нормальный. Есть шероховатости, но они постепенно устраняются. Те, кто программируют на Rust, дискомфорта не испытывают. Я думаю вы, как и многие из новичков, путаете "плохой синтаксис" с "непонятная/непривычная семантика". Во всяком случае уже не раз были споры насчет синтаксиса и никто из критиков не предложил лучшего решения для выражения тех же сущностей так, чтобы новый синтаксис не рушил остальные конструкции языка в других местах. Может быть у вас есть конструктивные предложения?


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

                                                                                      +10

                                                                                      Герб Саттер предложил:


                                                                                      template<Pointer T>
                                                                                      void swap(T& a, T& b)
                                                                                      [[gsl::post(lifetime(deref_a,{deref_b}))]] [[gsl::post(lifetime(deref_b,{deref_a}))]]
                                                                                      { .. }

                                                                                      Можете сравнить с вариантом из раста:


                                                                                      fn swap<T>(x: &mut T, y: &mut T) { .. }
                                                                                        –2

                                                                                        Давайте и я предложу:


                                                                                          –2

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

                                                                                            0

                                                                                            Парсер языка Rust, кстати, относительно простой.

                                                                                              0

                                                                                              Уж точно проще, чем у С++ :)

                                                                                          0
                                                                                          По ссылке такого кода нет Поиск не работает нормально (

                                                                                          Выглядит ужасно, да.

                                                                                          Но похоже, что затронет в основном библиотечный код, а не пользовательский.
                                                                                        +3
                                                                                        «Safe mode» в rust как я понимаю просто является встроенным в язык статическим анализатором кода не особо отличающийся(?) от таких же анализаторов на с++.

                                                                                        Лично я о чём-то аналогичном borrow checker для C++, слышал только в контексте lifetime profiler от Герба Саттера, и он работает так себе, легко сконструировать код и с ложно-положительными, и с ложно-отрицательными результатами.

                                                                                          +7

                                                                                          Несмотря на то, что я в расте новичок, готов утверждать, что с безопасностью (а точнее со способами выстрелить себе в ногу) у раста всё существенно лучше, чем в плюсах. В safe rust получить UB/segfault/data races не проще чем в каких-нибудь Java/Python/C#. Статический анализ тут не причём, это именно проверка валидности программа с точки зрения компилятора. Для сравнения — вы получаете в Джаве ошибку, когда класс имплементит интерфейс, но не реализует его метод. Такая ситуация может быть валидна синтаксически, но интерпретатор ваш код отвергнет.
                                                                                          При этом в Safe Rust, наверное, можно сделать всё что угодно, кроме FFI-вызовов. А Unsafe же приходится использовать весьма редко. Например я попробовал посмотреть в https://github.com/openethereum/openethereum — и там на ~120000 строк rust-кода нашлось 22 вхождения слова unsafe (из которых некоторые даже не относятся к коду). Так что если у вас всё же что-то пойдёт не так, то есть довольно мало мест, на которые надо обратить пристальное внимание.

                                                                                            +8

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


                                                                                            Модель владения/заимствования прекрасно работает и отсекает на корню отстрелы ног, рук и других частей тела, которые в C++ делаются на раз-два.


                                                                                            Если будет время, посмотрите лекции Кладова про Rust, там на простых примерах всё разбирается.


                                                                                            Начать можно отсюда:
                                                                                            https://youtu.be/WV-m7xRlXMs?t=1875


                                                                                            Метка времени по ссылке как раз на моменте, где приводится пример кода на C++ и Rust. В C++ вы получаете UB, в Rust такой код не компилируется с человеческим объяснением в чём программист не прав.


                                                                                            Это очень круто, когда можно писать код и не становиться с каждым разом всё больше и больше параноиком "а есть ли в моём коде UB и прострелы памяти? А всё ли я делаю правильно и ничего ли не забыл?". Даже опытные разработчики на C++ периодически совершают ошибки и наступают на одни и те же грабли. Rust освобождает от этой боли.

                                                                                              +5

                                                                                              Знакомые плюсоиды на такое отвечают "Да такое никто никогда не напишет", "Видео же, что ошибка", "У меня в программе 99% ошибок в логике, а с памятью никогда никаких проблем не бывает".

                                                                                                –1
                                                                                                Ваш пример уже устарел.

                                                                                                Вы получаете ошибку компиляции и в С++.
                                                                                                  +3

                                                                                                  (в n-ый раз, но всё-таки)


                                                                                                  Хочу получить ошибку компиляции тут, не подскажете, с каким компилятором и какими ключами собирать?

                                                                                                    –6
                                                                                                    Во-первых, в учебниках написано, что string_view не рекомендуется возвращать по значению, надо возвращать string, как раз из-за подобных случаев.

                                                                                                    Во-вторых, надо таки читать учебники, чтобы писать программы правильно.

                                                                                                    В-третьих, Вы слишком много ожидаете от _первого_ релиза DFA.
                                                                                                      +6

                                                                                                      О, началась знакомая риторика.


                                                                                                      В-третьих, Вы слишком много ожидаете от _первого_ релиза DFA.

                                                                                                      Возможно, но для более простого примера — когда shortest и cut_prefix вызываются непосредственно — профилировщик лайфтаймов от Герба Саттера таки ловит ошибку, пусть и с ложно-положительным срабатыванием, а -fanalyzerнет, так что какие-то ожидания у меня всё-таки были.


                                                                                                      Во-вторых, надо таки читать учебники, чтобы писать программы правильно.

                                                                                                      Ух, сколько гонора. Гм, а не подскажете, где можно найти учебник, который бы покрывал C++17? C++ core guidelines — это не учебник и не завершённая вещь.


                                                                                                      Во-первых, в учебниках написано, что string_view не рекомендуется возвращать по значению, надо возвращать string, как раз из-за подобных случаев.

                                                                                                      То есть string_view::substr должен возвращать string, я понял. А если серьёзно — ну что не так с example_ok по ссылке выше? Тут создаётся временная строка, да, но результат cut_prefix заведомо держит ссылку на данные str, а не n_str, так что тут всё в порядке. Собственно, если расписать типы shortest и cut_prefix, то в Rust им можно приписать разные типы:


                                                                                                      fn shortest<'a>(a: &'a str, b: &'a str) -> &'a str {
                                                                                                          if a.len() < b.len() { a } else { b }
                                                                                                      }
                                                                                                      
                                                                                                      fn cut_prefix<'a>(prefix: &str, s: &'a str) -> &'a str {
                                                                                                          if s.starts_with(prefix) {
                                                                                                              &s[prefix.len()..]
                                                                                                          } else {
                                                                                                              s
                                                                                                          }
                                                                                                      }

                                                                                                      То есть информация о связях между аргументом и результатом непосредственно выражена в типе. И компилятор Rust эту информацию эксплуатирует:


                                                                                                      // компилируется
                                                                                                      fn example_ok(n: u32, s: &str) -> &str {
                                                                                                          let n_str = n.to_string();
                                                                                                          cut_prefix(&n_str, s)
                                                                                                      }
                                                                                                      // не компилируется: "cannot return value referencing local variable `n_str`"
                                                                                                      fn example_err(n: u32, s: &str) -> &str {
                                                                                                          let n_str = n.to_string();
                                                                                                          shortest(&n_str, s)
                                                                                                      }

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


                                                                                                      fn example_hof_ok(
                                                                                                          n: u32,
                                                                                                          s: &str,
                                                                                                          func: for<'a> fn(&str, &'a str) -> &'a str
                                                                                                      ) -> &str {
                                                                                                          let n_str = n.to_string();
                                                                                                          func(&n_str, s)
                                                                                                      }
                                                                                                      
                                                                                                      fn example_hof_err(
                                                                                                          n: u32,
                                                                                                          s: &str,
                                                                                                          func: for<'a> fn(&'a str, &'a str) -> &'a str
                                                                                                      ) -> &str {
                                                                                                          let n_str = n.to_string();
                                                                                                          func(&n_str, s)
                                                                                                      }

                                                                                                      То есть в example_hof_ok тип функции func прямо говорит, что возвращаемое значение связано лишь со вторым аргументом, а в example_hof_err функция func может возвращать данные, связанные с любым аргументом. example_hof_err не компилируется, и для проверки даже не нужно её вызывать. Ну и, опять таки из-за того, что отношения выражены в типах, мы можем вызвать example_hof_ok с cut_prefix и не можем с shortest (тык).


                                                                                                      Вообще, строковые срезы в программах на Rust применяются достаточно активно именно потому, что компилятор ловит подобные ошибки. У программиста на C++ тут две альтернативы, и обе так себе: либо полагаться на себя и ненадёжный статанализатор, либо на всякий случай таки делать string и зря выделять память в куче.

                                                                                                        –1
                                                                                                        Я не понимаю простыню, поясните — или Вы не можете явно прописать причинно-логическую связь между вводом и выводом, или думаете, что компилятор может быть умнее Вас?? Или даже хотите ???
                                                                                                          +1

                                                                                                          Если компилятор будет умнее меня — я буду только рад.

                                                                                                            0

                                                                                                            вы уверены что сможете с этим работать соблюдая какие-то вменяемые сроки? Пробовали действительно умные компиляторы — idris какой-нибудь, или ATS?

                                                                                                              0

                                                                                                              А это другой вопрос. На том же идрисе никто не мешает писать как на хаскеле, без всей этой зависимо типизированной ерунды.

                                                                                                              –4
                                                                                                              мне жаль тебя, я предпочитаю быть в команде разработчиков компилятора, ну а в данном случае — отлавливаю(препарирую) ошибки в дизайне раста (и прочих)
                                                                                                              +2

                                                                                                              Компилятор бесконечно внимательнее меня, как минимум.

                                                                                                          –1
                                                                                                          Перевел ваш пример на Rust.
                                                                                                            0

                                                                                                            Спасибо, надо всё-таки побыстрее комментарии писать.

                                                                                                          +1

                                                                                                          Вы про статический анализ в GCC 10? Это пока ещё довольно сырая штука, отлавливает далеко не всё, а на выхлоп по прежнему больно смотреть:


                                                                                                          … Первоначально это было 1187 строк. Я исправлял различные ошибки и реализовывал больше упрощений, чтобы довести его до 170 строк.

                                                                                                          Это по прежнему "костыль" в виде статического анализа, прикрученный сбоку, который будет давать false positive или пропускать ошибки в тех или иных случаях. Более того, мало просто добавить статический анализ в свой workflow, нужно закладывать время программиста на обработку его результатов.


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

                                                                                                            –4
                                                                                                            В то время как Rust на уровне компилятора предоставляет гарантии корректности.
                                                                                                            Да для 20% случаев ошибок, но цена этого что то вроде вашей девственности при _каждой_ компиляции.
                                                                                                            Я с подобным не согласен.
                                                                                                            +1
                                                                                                            Только вот оно false positive выдает на раз два.
                                                                                                              0
                                                                                                              Тут в натуре ошибка, итератор инвалидирован инвазивной операцией ((
                                                                                                        –1
                                                                                                        Посмотрите на следующий код, который написан на языке, считающимся высокоуровневым:
                                                                                                        sleep(5);

                                                                                                        И сравните с тем, как то же самое поведение реализуется в Rust:
                                                                                                        use std::{thread, time::Duration};
                                                                                                        thread::sleep(Duration::from_secs(5));
                                                                                                        


                                                                                                        Где вам понятнее, что происходит и где по-вашему вероятность ошибиться меньше? Мне кажется, что ответ очевиден.


                                                                                                        Эм… в первом случае, очевидно, понятнее? Но судя по общему тону статьи, автор намекает на второй вариант, что весьма странно, как по мне.
                                                                                                          +1
                                                                                                          По-моему тоже пример неудачный. Разве что `5` действительно вызывает некоторое разночтение, но т.к. обычно sleep в мс, то при модификации первого примера на sleep(5*MSEC_PER_SEC); всё становится очевидно и без этого жуткого оверхеда.
                                                                                                          Если что, я желаю всяческих удач Расту и кармически несовместим с С++, но именно данный пример скорее похож на словоблудие Явы
                                                                                                            +2

                                                                                                            Вот вы и попались: "так как обычно sleep в мс". Пример взят из проблемных мест PHP, там в sleep передается задержка в секундах.

                                                                                                              0
                                                                                                              Я и не спорю, что голые константы, в сочетании с дурацки спроектированными функциями, плохи.
                                                                                                              Просто здесь Растовый вариант выглядит уж очень громоздким.
                                                                                                                +1
                                                                                                                Справедливости ради, это просто демонстрация строгой типизации, которая много где есть. В любом типизированном языке можно написать sleep, принимающий на вход аргумент с типом «промежуток времени» и решить эту проблему; точно также как в расте можно написать sleep, принимающий на вход u32. Так что конкретно это не делает раст «универсальным» языком.
                                                                                                                  0

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

                                                                                                                    0
                                                                                                                    Да, типы объявляются достаточно лаконично. Но я все равно могу написать функцию sleep с аргументом u32.
                                                                                                                      +2

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


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

                                                                                                                        0
                                                                                                                        Если взять язык, который реально озабочен производительностью, а не просто вид делает — то в C++ накладок нет. Также их можно избежать в C#, насколько я знаю. В JVM с этим проблемы, да, но если Вы пишете под JVM, то это не самая Ваша серьезная проблема. Ну а про скрипты и говорить нечего.
                                                                                                                          +2
                                                                                                                          Нельзя избежать оверхеда на C# абстракции и рантайм вносят свою лепту. Можно только приближаться по производительности к нативному коду. И инструментарий языка будет ограничен практически только конструкция для работы на стеке предаллоцированные массивы, struct, ref struct и span'ы.

                                                                                                                          В сравнении с такими абстракциями программировать на расте гораздо веселее.
                                                                                                                            +1
                                                                                                                            Если взять язык, который реально озабочен производительностью, а не просто вид делает — то в C++ накладок нет.

                                                                                                                            Там полно накладок с другим.

                                                                                                                              0
                                                                                                                              Например?
                                                                                                                                +3

                                                                                                                                Ну, например, то, что я уже приводил. Ну или вот.

                                                                                                                                  0
                                                                                                                                  Ну то есть не относящиеся к производительности. ОК, принято.
                                                                                                                                    0

                                                                                                                                    Да не, почему, накладки с производительностью тоже есть

                                                                                                                                      0
                                                                                                                                      Ну вот разве что restrict-проблемы, это да. Которые решаются использованием локальных переменных.
                                                                                                                                        0

                                                                                                                                        Ну и как проблему на видео пофиксить локальными переменными? В Rust, если что, такой проблемы нет.

                                                                                                                                          0

                                                                                                                                          А можно пример, как локальны переменные с этим помогут? Особенно в случае char* вместо int*.

                                                                                                                                            0
                                                                                                                                            Я имею в виду что-то вроде этого. Компилятор перечитывает значения из a, потому что возможно что a == b. Поэтому приходится читать лишь один раз и кэшировать значение локально.
                                                                                                                                              0

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

                                                                                                                                        0

                                                                                                                                        Как сравнивать два POD'а не почленно?


                                                                                                                                        Потому что сравнивать почленно — надеяться на компилятор, а он умудряется обделаться даже на предельно банальном случае:


                                                                                                                                        struct Foo
                                                                                                                                        {
                                                                                                                                            int f1;
                                                                                                                                            int f2;
                                                                                                                                        };
                                                                                                                                        
                                                                                                                                        bool operator==(const Foo& l, const Foo& r)
                                                                                                                                        {
                                                                                                                                            return l.f1 == r.f1 && l.f2 == r.f2;
                                                                                                                                        }

                                                                                                                                        clang 9 компилирует в разумный


                                                                                                                                        mov     rax, qword ptr [rdi]
                                                                                                                                        cmp     rax, qword ptr [rsi]
                                                                                                                                        sete    al
                                                                                                                                        ret

                                                                                                                                        а clang 10 (более новый, заметьте!) делает какую-то полную фигню


                                                                                                                                        movq    xmm0, qword ptr [rdi]   # xmm0 = mem[0],zero
                                                                                                                                        movq    xmm1, qword ptr [rsi]   # xmm1 = mem[0],zero
                                                                                                                                        pcmpeqd xmm1, xmm0
                                                                                                                                        pshufd  xmm0, xmm1, 80          # xmm0 = xmm1[0,0,1,1]
                                                                                                                                        movmskpd        eax, xmm0
                                                                                                                                        cmp     al, 3
                                                                                                                                        sete    al
                                                                                                                                        ret

                                                                                                                                        Но это всё, конечно, не сравнится с gcc 10, вот где топ:


                                                                                                                                                mov     edx, DWORD PTR [rsi]
                                                                                                                                                xor     eax, eax
                                                                                                                                                cmp     DWORD PTR [rdi], edx
                                                                                                                                                je      .L5
                                                                                                                                                ret
                                                                                                                                        .L5:
                                                                                                                                                mov     eax, DWORD PTR [rsi+4]
                                                                                                                                                cmp     DWORD PTR [rdi+4], eax
                                                                                                                                                sete    al
                                                                                                                                                ret

                                                                                                                                        На этом языке предлагается писать производительный код? Серьёзно?

                                                                                                                                          0
                                                                                                                                          Да ладно, думаю Вы знаете :)
                                                                                                                                          Код
                                                                                                                                          bool operator==(const Foo& l, const Foo& r)
                                                                                                                                          {
                                                                                                                                              return memcmp(&l, &r, sizeof(Foo)) == 0;
                                                                                                                                          }

                                                                                                                                          Вообще тут виноват компилятор ©, а не язык (ну или я опять о чем-то не знаю).
                                                                                                                                            0
                                                                                                                                            memcmp

                                                                                                                                            Не имею права. А вдруг паддинг?


                                                                                                                                            Вообще тут виноват компилятор ©, а не язык (ну или я опять о чем-то не знаю).

                                                                                                                                            Ну так-то и хаскель — прекрасный для производительности язык, просто компилятор ещё недостаточно умный.

                                                                                                                                  +1
                                                                                                                                  Если взять язык, который реально озабочен производительностью, а не просто вид делает — то в C++ накладок нет.

                                                                                                                                  ИМХО самая большущая накладка — clone by default.