Сигма-тип и вы


Давайте поговорим о простой, но мощной концепции в программировании — сигма-типах.

Сигма-тип (тип-сумма, меченное объединение) может содержать значения одного и только одного из нескольких типов. Например, рассмотрим настройки в INI-подобном файле конфигурации. Пусть каждая настройка может быть строкой, целым или булевым значением. Если бы мы хотели сделать свою библиотеку на C++, мы бы написали что-то вроде этого:

struct Setting {
    union {
        string str;
        int num;
        bool b;
    };
    enum Type { Str, Int, Bool };
    Type tag;
};

// Отображение настроек на их имена
using Settings = unordered_map<string, Setting>;

Это опасный путь, ибо всегда нужно помнить о нескольких вещах:

  • Обновить tag при присваивании нового значения.
  • Читать из объединения только корректный тип в соответствии с тегом.
  • Вовремя вызывать конструкторы и деструкторы для всех нетривиальных типов. В данном примере это только string, но их могло быть и больше.

Если забыть хоть один из этих пунктов, объект окажется в некорректном состоянии, и там будет плач и скрежет зубов. Всю эту магию можно инкапсулировать и работать с типом через набор методов вроде getType(), asBool(), asString(), которые тоже выглядят громоздко. Тем более, такое решение всего лишь перекладывает проблему на того, кто будет реализовывать эти методы. Ему всё равно придётся поддерживать инварианты без какой либо помощи со стороны языка.

Было бы куда лучше, если бы сигма-тип общего назначения был в стандартной библиотеке. В C++17 мы наконец его получили! Он называется std::variant, и сейчас мы познакомимся с ним поближе.

Использование std::variant


variant — это шаблон класса, который в качестве шаблонных параметров принимает типы, которые он может содержать. Вместо кода из примера выше мы могли бы определить тип настройки как variant<string, int, bool>. Присваивание значения в variant работает вполне ожидаемо:

variant<string, int, bool> mySetting = string("Hello!"); // или
mySetting = 42; // или
mySetting = false;

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

match (theSetting) {
    Setting::Str(s) =>
        println!("A string: {}", s),
    Setting::Int(n) =>
        println!("An integer: {}", n),
    Setting::Bool(b) =>
        println!("A boolean: {}", b),
};

но это не про C++17(*). Вместо этого нам дали вспомогательную функцию std::visit. Она принимает variant, который нужно обработать, и объект-посетитель, который является вызываемым для любого типа в переданном variant.

Как определить объект-посетитель? Один из способов — создать объект, в котором оператор вызова перегружен для всех нужных типов:

struct SettingVisitor {
    void operator()(const string& s) const {
        printf("A string: %s\n", s.c_str());
    }

    void operator()(const int n) const {
        printf("An integer: %d\n", n);
    }

    void operator()(const bool b) const {
        printf("A boolean: %d\n", b);
    }
};

Выглядит ужасно многословно, и становится ещё хуже, если нам нужно захватить или изменить какое-то внешнее состояние. Хмм… лямбды великолепно справляются с захватом состояния. Может можно сделать из них объект-посетитель?

make_visitor(
    [&](const string& s) {
        printf("string: %s\n", s.c_str());
        // ...
    },
    [&](const int d) {
        printf("integer: %d\n", d);
        // ...
    },
    [&](const bool b) {
        printf("bool: %d\n", b);
        // ...
    }
)

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

template <class... Fs>
struct overload;

template <class F0, class... Frest>
struct overload<F0, Frest...> : F0, overload<Frest...>
{
    overload(F0 f0, Frest... rest) : F0(f0), overload<Frest...>(rest...) {}

    using F0::operator();
    using overload<Frest...>::operator();
};

template <class F0>
struct overload<F0> : F0
{
    overload(F0 f0) : F0(f0) {}

    using F0::operator();
};

template <class... Fs>
auto make_visitor(Fs... fs)
{
    return overload<Fs...>(fs...);
}

Здесь мы воспользовались шаблонами с переменным количеством параметров, которые появились в C++11. Их нужно определять рекурсивно, поэтому мы сначала объявляем базу рекурсии F0, а затем объявляем набор конструкторов, каждый из которых откусывает один элемент из списка параметров шаблона и добавляет его к типу в качестве оператора вызова.

Это выглядит хлопотно, но не отчаивайтесь! C++17 вводит новый синтаксис, который сократит весь этот код до

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

Просто, не правда ли? Если же вам не по душе ни один из этих вариантов, воспользуйтесь условным оператором времени компиляции (if constexpr) из стандарта C++17:

[](auto& arg) {
    using T = std::decay_t<decltype(arg)>;

    if constexpr (std::is_same_v<T, string>) {
        printf("string: %s\n", arg.c_str());
        // ...
    }
    else if constexpr (std::is_same_v<T, int>) {
        printf("integer: %d\n", arg);
        // ...
    }
    else if constexpr (std::is_same_v<T, bool>) {
        printf("bool: %d\n", arg);
        // ...
    }
}

Выглядит лучше?

Нет


Вся эта канитель для std::visit — полнейшее безумие. Мы начали с простой цели: посмотреть на содержимое сигма-типа. А чтобы завершить эту скромную миссию, нам пришлось:

  1. Определить вызываемый объект, включающий много однообразного кода, или
  2. Определить поведение с помощью лямбд, что потребовало:
    • Понимания шаблонов с переменным количеством параметров во всей полноте их рекурсивного великолепия, или
    • Близкого знакомства с using-объявлениями с переменным количеством параметров, которые только появились в стандарте C++17.

    или
  3. Использовать ветвление времени компиляции, для чего требуется знание и глубокое понимание синтаксиса constexpr if, а так же всяких интересностей из type_traits, вроде std::decay.

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

Как мы вообще тут оказались?


У меня нет цели унизить людей из комитета ISO C++, которые выбрали такой подход. Я пил пиво с некоторыми из них, и могу сказать что это хорошие, разумные и трудолюбивые люди. Я уверен, что упускаю какой-то важный контекст, ибо я ни разу не был на обсуждениях стандарта и не читал всей сопутствующей документации. Но с точки зрения внешнего наблюдателя, несоответствие между сложностью задачи и предложенным решением — это какая-то дичь. Как можно обучить этому, не перегружая новичка кучей… дополнительного материала? Должен ли среднестатистический программист знать всё это? А если целью добавления variant в стандарт не было создание инструмента для массового пользователя, то нужен ли он вообще? Как минимум, если у комитета C++17 не было времени и ресурсов для введения сопоставления с образцом в язык, им следовало хотя бы добавить в стандарт что-то вроде make_visitor. Но и это было оставлено в качестве упражнения для пользователя.

И всё же как мы дошли до жизни такой? Я предполагаю, что дело в психологической склонности человека к подтверждению своей точки зрения. Возможно, когда несколько достаточно грамотных людей, которые знают как работает SFINAE, и не пугаются при виде чего-то такого:

template <typename F>
typename std::enable_if<!std::is_reference<F>::value, int>::type
foo(F f)
{
    // ...
}

собираются вместе, у них получается std::visit. Было бы безумием ожидать, что обычный пользователь языка будет городить с помощью рекурсивных шаблонов перегруженный вызываемый объект, чтобы всего лишь понять, int или string храниться вот в этой штуке.

Я не буду говорить, что переусложнённость C++ является благом, но он явно гораздо сложнее, чем следовало бы. Скотт Мейерс, автор книг Effective C++ и Effective Modern C++, тоже высказал подобные мысли в недавних лекциях. Вслед за Мейерсом, я уверен, что каждый член комитета всеми силами старается избегать ненужной сложности и сделать язык как можно проще в использовании. Но это трудно сказать, глядя на результат их работы. Беспричинная сложность продолжает нарастать.

Куда мы движемся?


Есть причина, по которой C++ используется настолько широко, особенно в системном программировании (*). Он чрезвычайно выразителен, оставляя при этом полный контроль над аппаратурой. Инструментарий, созданный для C++, является наиболее развитым среди всех языков программирования, кроме C. Он поддерживает невообразимое количество платформ.

Но если отставить весь этот исторический багаж, мы увидим некоторые серьёзные недостатки. Поразбирайтесь с D, и вы быстро поймёте, что метапрограммирование не требует самоистязания и безумного синтаксиса. Поиграйте немного с Rust, и вы почувствуете, что unique_ptr и shared_ptr (которые сами по себе были глотком свежего воздуха), выглядят как неудачная шутка. Совсем непристойно в 2017 году работать с зависимостями, буквально копируя содержимое одних файлов в другие с помощью директивы #include.

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

Несмотря на всё это, я буду убеждать своих коллег использовать variant, если в нём возникает нужда. Сигма-типы — очень полезная вещь, и они стоят затрачиваемых усилий. Как сказал Джон Кальб, «если вы не можете пользоваться языком с уродливыми бородавками, возможно вам не следует писать на С++».

Примечания


1. Термин "$\Sigma$-тип" пришёл из теории типов, с помощью которой можно описать типизацию в языка программирования. Если тип может содержать или значения типа A или значения типа B, то множество его возможных состояний есть теоретико-множественная сумма всевозможных состояний типов A и B. Вам наверняка знакомы «двойники» сигма-типов: типы-произведения, то есть структуры, кортежи и т.п. Например, множество возможных состояний структуры из типов A и B содержит декартово произведение состояний типов A и B.

2. Есть предложение P0095R1 о внесении сопоставления с образцом в Стандарт C++.

3. Обязательный к просмотру доклад Скотта Мейерса

Комментарии (246)


  1. DrLivesey
    30.06.2018 10:30

    В одном из заголовков написано «движимся». Исправьте, пожалуйста.
    К сожалению, не нашел в приложении как написать в личку.


    1. Sdima1357
      30.06.2018 10:52
      +1

      В личку
      Можно и так
      1 Нажать на иконку автора(зайти в его профиль)
      2 Кнопка «написать»
      Появится в диалогах автора и в Ваших


      1. DrLivesey
        30.06.2018 10:54

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


        1. Sdima1357
          30.06.2018 11:08

          видимо как и в статье, простота использования конфликтует с предоставляемыми возможностями.
          То есть или — или. Или теорема о не-существовании серебряной пули


    1. SergeySib Автор
      30.06.2018 11:51

      Спасибо, поправил.


  1. kozyabka
    30.06.2018 11:05

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

    значения чего?


    1. Sdima1357
      30.06.2018 11:15

      Значение переменной сигма-типа может иметь только тип перечисленный в декларации.


    1. nexmean
      30.06.2018 11:47

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


      enum SumType {
          Int(i64), // конструктор SumType содержащего i64
          String(String), // констуктор SumType содержащего String
          Nothing, // конструктор SumType ~~не~~ содержащего ничего
      }
      
      fn main() {
          let string = SumType::String("Hello, World!");
          match string {
              SumType::String(s) => println!("It's a string: {}", s),
              SumType::Int(i) => println!("It's an integer: {}", i),
              SumType::Nothing => println!("It's nothing"),
          };
      }

      1. Например множества значений типов Int64 и Float64 в компьютере полностью пересекаются, ибо представлены последовательностью 1 и 0 одного размера.


      1. graninas
        30.06.2018 16:28

        > конструкторов типа

        Конструкторов значений типа, все же. Конструктором типа там чуть-чуть иное называется.
        Но не суть.


        1. nexmean
          30.06.2018 17:32

          Всё верно, конструкторы типа это уже из области HKT. Спасибо за замечание.


  1. eao197
    30.06.2018 11:48
    +2

    Не понятен смысл перевода этого материала. Это же формат личного блога: личное мнение автора блога в виде сетований на то, что хотелось бы лучше, а имеем как всегда. Собственно, такая заметка в чьем-то персональном блоге — это нормально. А в виде самостоятельной статьи… В чем постановка задачи/проблемы, в чем решение, какие выводы?

    Ну и еще из непонятого (хотя очевидно, что это вопрос не переводчику, но все-таки): а чем C++ные unique_ptr и shared_ptr хуже Rust-овских Box/Rc/Arc? Вроде как тот же самый фаберже, только в профиль.


    1. SergeySib Автор
      30.06.2018 13:31
      +2

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

      Более того, в качестве «побочного эффекта», из этой статьи я вынес для себя идею make_visitor, и увидел хороший пример использования variadic using.

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


      1. eao197
        30.06.2018 13:40

        Более того, в качестве «побочного эффекта», из этой статьи я вынес для себя идею make_visitor, и увидел хороший пример использования variadic using.
        Вроде как все тоже самое расписано в документации к std::visit на cppreference.
        В Rust для небезопасного разделения владения объектом нужно совершить лишние телодвижения. В C++ наоборот, чтобы приблизить семантику к Rustовской, приходится совершать дополнительные действия, например использовать умные указатели.
        Простите, тут не распарсил. Вы хотите сказать, что в C++ когда нужно иметь ссылку на один и тот же объект, то люди, чтобы не парится с контролем времени жизни, тупо размещают объект в динамической памяти и используют shared_ptr?


        1. SergeySib Автор
          30.06.2018 14:02

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

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


          1. eao197
            30.06.2018 14:17
            +1

            И опять не понял. В Rust-е, afaik, типы Box/Rc/Arc так же являются частью стандартной библиотеки языка. И используются для тех же самых целей. Так почему же «и вы почувствуете, что unique_ptr и shared_ptr (которые сами по себе были глотком свежего воздуха), выглядят как неудачная шутка»?

            Понятно, что это не ваши слова, но раз вы посчитали статью достойной перевода (почему-то), то остается спрашивать у вас.


            1. PsyHaSTe
              01.07.2018 11:28

              Ну, во-первых Box редко используется.
              Во-вторых ручные Rc/Arc тоже достаточно редко используются, когда нужно разрулить рекурсивные ссыкли (типичная история — реализовать граф на расте).

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


              1. eao197
                01.07.2018 11:38

                Ну, во-первых Box редко используется.
                Во-вторых ручные Rc/Arc тоже достаточно редко используются,

                Не понятно, как все это соотносится с «и вы почувствуете, что unique_ptr и shared_ptr (которые сами по себе были глотком свежего воздуха), выглядят как неудачная шутка»

                Я писал телеграм-бота, так у меня там один-единственный rc для разделяемого состояния используется во всей программе.
                В C++ не проблема написать программу, в которой программист вообще ничего сам динамически создавать не будет — будет только размещение объектов на стеке или композиция внутри других объектов, а все остальное будет где-то в дебрях библиотек.

                Так что апелляция к вашему личному опыту ничуть не прояснило данный вопрос.


                1. PsyHaSTe
                  01.07.2018 11:40

                  Не понятно, как все это соотносится с «и вы почувствуете, что unique_ptr и shared_ptr (которые сами по себе были глотком свежего воздуха), выглядят как неудачная шутка»

                  Видимо разница в том, что у вас семантика как у unique_ptr и shared_ptr, только из коробки. В С++ выбор или забить и использовать сырые указатели, или везде иметь кучу этих самых unique_ptr, что не очень удобно.

                  В C++ не проблема написать программу, в которой программист вообще ничего сам динамически создавать не будет — будет только размещение объектов на стеке или композиция внутри других объектов, а все остальное будет где-то в дебрях библиотек.

                  А кто-то говорил про программу без динамических аллокаций?


                  1. eao197
                    01.07.2018 11:48
                    -2

                    Видимо разница в том, что у вас семантика как у unique_ptr и shared_ptr, только из коробки.

                    Вы какими-то криптограммами говорите. Отдельные слова понятны, а что хотели сказать — не ясно.
                    В С++ выбор или забить и использовать сырые указатели, или везде иметь кучу этих самых unique_ptr, что не очень удобно.
                    О каком выборе вы говорите? Такое ощущение, что вы ни C++, ни unique_ptr/shared_ptr толком и не видели.
                    А кто-то говорил про программу без динамических аллокаций?
                    Простите, может вы бот? Вы тогда понятна невнятность ваших ответов.

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


                  1. alexeykuzmin0
                    01.07.2018 14:07

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


                    1. PsyHaSTe
                      01.07.2018 14:14

                      Ну я за всю Одессу, конечно, не скажу, но "синтаксис подлиннее" для такой базовой вещи, как ссылка — как мне кажется, очень важно. Плюс, насколько я знаю, это накладывает оверхед в ратнайме. А в том же расте компилятор не ведет подсчет ссылок, а просто генерирует drop(x) в некотором месте, где он вывел смерть переменной с учетом всего этого.


                      1. alexeykuzmin0
                        01.07.2018 14:22

                        Ну я за всю Одессу, конечно, не скажу, но «синтаксис подлиннее» для такой базовой вещи, как ссылка — как мне кажется, очень важно.
                        Согласен, кому-то это может быть важно.
                        это накладывает оверхед в ратнайме
                        unique_ptr имеет оверхед в рантайме только в случае наличия нетривиального делетера — при конструировании его нужно скопировать (один указатель и одно копирование этого указателя). Уничтожение и использование полностью бесплатны.


                        1. PsyHaSTe
                          01.07.2018 14:22

                          Про unique понятно. А что про shared?


                          1. alexeykuzmin0
                            01.07.2018 14:26
                            +1

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


                            1. PsyHaSTe
                              01.07.2018 14:33
                              +1

                              В этой главе подробно объясняется. Если останется непонятно — постараюсь объяснить в меру своего понимания.


                              1. alexeykuzmin0
                                01.07.2018 15:22

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

                                pub struct S {
                                    link: & int; // иммутабельная ссылка
                                };
                                
                                static mut vec : Vec<S> = None; // глобальный массив объектов, которые будут разделять владение переменной
                                
                                fn create_vector() {
                                    vec = Vec::new(); // создаем массив
                                    let x = 170239; // переменная, на которую будем ссылаться
                                    for i in 0..10 {
                                        let s = S::new();
                                        s.link = &x;
                                        vec.push(s);
                                    }
                                }
                                
                                fn change_vector() {
                                    let x = 239017; // другая переменная, некоторые ссылки из массива мы обновим на нее
                                    for i in 0..20 {
                                        // обновляем случайный элемент
                                        let ind = rand::random() % 10;
                                        let s = S::new();
                                        s.link = &x;    
                                        vec[ind] = s;    
                                    }
                                }
                                

                                То есть, на этапе компиляции мы не знаем, на какой итерации у нас не останется ни одной ссылки на 170239 (и будет ли такая итерация вообще). Каким образом обеспечивается гарантия того, что 170239 будет уничтожена вовремя, без оверхеда по памяти или времени?


                                1. PsyHaSTe
                                  01.07.2018 15:55
                                  +1

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


                                  Во-вторых этот код не соберется, потому что x будет уничтожен после выхода из функции create_vector, а dangling pointers запрещены.


                                  1. alexeykuzmin0
                                    01.07.2018 16:53

                                    В расте нет мутабельных глобальных переменных, поэтому такой код вообще не соберется
                                    Давайте поместим все в класс. Вектор будет его полем, все функции — методами.
                                    Во-вторых этот код не соберется, потому что x будет уничтожен после выхода из функции create_vector, а dangling pointers запрещены.
                                    А как тогда вообще в Rust сделать хоть какое-нибудь разделяемое владение?


                                    1. nexmean
                                      01.07.2018 17:11
                                      +1

                                      А как тогда вообще в Rust сделать хоть какое-нибудь разделяемое владение?

                                      В safe коде Rc<RefCell<T>> или для многопоточности Arc<Mutex<T>> если нужно разделяемое владение с возможностью мутировать. Для иммутабельного разделяемого владения достаточно Rc<T> или Arc<T>. То есть для мутабельного разделяемого владения в safe коде на расте всегда нужен контейнер который в рантайме будет проверять, что никто не захватил ссылку с возможностью мутировать память перед тем, как дать тебе доступ к ней.


                                      1. alexeykuzmin0
                                        01.07.2018 18:40

                                        Давайте тогда рассмотрим такой код:

                                        pub struct S {
                                            link: Rc<RefCell<int>>; // иммутабельная ссылка
                                        };
                                        
                                        struct S2 {
                                            mut vec : Vec<S> = None; // глобальный массив объектов, которые будут разделять владение переменной
                                        }
                                        
                                        impl S2 {
                                            fn create_vector() {
                                                vec = Vec::new(); // создаем массив
                                                let x = 170239; // переменная, на которую будем ссылаться
                                                for i in 0..10 {
                                                    let s = S::new();
                                                    s.link = &x;
                                                    vec.push(s);
                                                }
                                            }
                                        
                                            fn change_vector() {
                                                let x = 239017; // другая переменная, некоторые ссылки из массива мы обновим на нее
                                                for i in 0..20 {
                                                    // обновляем случайный элемент
                                                    let ind = rand::random() % 10;
                                                    let s = S::new();
                                                    s.link = &x;    
                                                    vec[ind] = s;    
                                                }
                                            }
                                        }
                                        
                                        Будет ли у нас оверхед в этом случае? Насколько я понимаю из слов уважаемого PsyHaSTe, нет. Но как оно тогда работает? Мы же на этапе компиляции не знаем, куда вставлять деструктор числа 170239.


                                        1. PsyHaSTe
                                          01.07.2018 18:48

                                          Rc даст оверхед, потому что он внутри занимается именно тем, о чем можно догадаться по аббревиатуре. Я говорил, что нет оверхеда при использовании примитивов языка, голых ссылок, а не контейнеров. В большинстве случаев нужны неизменяемые шарящиеся ссылки, с чем прекрасно справляются &. Если нужен шарящийся мутабельный стейт — то тут уж, конечно, без счетчика и рантайм оверхеда не обойтись.


                                          1. alexeykuzmin0
                                            01.07.2018 18:58

                                            То есть, ссылки из Rust означают эксклюзивное владение объектом, и бесплатны, так же, как и std::unique_ptr. Rc из Rust дает множественное владение, и не бесплатен, так же, как и std::shared_ptr. Тогда я не понимаю, в чем ваши претензии к c++. Ну да, shared_ptr не бесплатен, ну так его и не надо использовать в местах, где у вас единственный владелец. И даже не из-за производительности, а потому, что читабельность кода страдать будет.


                                            1. PsyHaSTe
                                              01.07.2018 19:05
                                              +1

                                              То есть, ссылки из Rust означают эксклюзивное владение объектом, и бесплатны

                                              Нет, не так.

                                              Смотрите, вот допустим я пишу такой код:

                                              fn print<T: Display>(value: &T) { println!("{}", value); }

                                              let a = "123";
                                              let b: i32 = a.parse();
                                              print(&a);
                                              print(&b);


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


                                              1. alexeykuzmin0
                                                01.07.2018 21:58

                                                Вот как я могу тот же самый код на C++ написать:

                                                template<typename T>
                                                void print(T* value) {
                                                    cout << *value;
                                                }
                                                ...
                                                auto a = make_unique<string>("123");
                                                auto b = parse(a.get());
                                                print(a.get());
                                                print(b.get());
                                                

                                                Как видите, никакие std::shared_ptr не нужны и все тоже происходит бесплатно. Так чем же std::unique_ptr и std::shared_ptr хуже, чем reference и Rc?


                                                1. PsyHaSTe
                                                  01.07.2018 22:33
                                                  +2

                                                  1. Что будет, если print меняет значение value? Например
                                                    template<typename T>
                                                    void print(T* value) {
                                                    cout << *value;
                                                    *value = 0;
                                                    }
                                                  2. Что будет, если print возвращает переданное значение?
                                                    template<typename T>
                                                    T* print(T* value) {
                                                    cout << *value;
                                                    return value;
                                                    }
                                                    ...
                                                    auto a = make_unique<string>("123");
                                                    auto b = parse(a.get());
                                                    auto cloneda = print(a.get());
                                                    *cloneda = {0};
                                                    print(b.get());


                                                  1. alexeykuzmin0
                                                    01.07.2018 22:39

                                                    1. Поменяется значение b. Если хочется, чтобы так делать было нельзя, можно передать по значению или добавить const.
                                                    2. Вернется не-владеющий указатель на b. Если хочется возвращать владеющий указатель, можно передать владение в print, а потом вернуть его обратно.
                                                    Если вы на что-то намекали, я вас не понял.


                                                    1. alexeykuzmin0
                                                      01.07.2018 22:46

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


                                                    1. PsyHaSTe
                                                      01.07.2018 23:29

                                                      1. насколько я знаю, const в С++ очень легко потерять. Хотя, конечно, некоторые гарантии дает
                                                      2. Почему не владеющий? Как по мне это просто сырой указатель, с которым можно делать всё, что угодно.

                                                      P.S. Карму выправил, кто минусует — непонятно, как по мне диалог у нас вполне продуктивный.


                                                      1. alexeykuzmin0
                                                        01.07.2018 23:39

                                                        1. Const можно потерять только посредством const_cast и C-style cast, если я ничего не забыл. Оба механизма запрещены Core C++ Guidelines, пункты ES48, ES49, ES50. По сути, можно считать, что это unsafe код.
                                                        2. Core C++ Guidelines, I11. Передача владения по сырому указателю или ссылке запрещена. Если очень хочется (например, нужно поддержать совместимость по ABI со старой библиотекой), нужно использовать бесплатную обертку gsl::owner<T*>.

                                                        PS: Я не намекал на это, но спасибо. Мне действительно важно понимать, если я делаю что-то не так, и обычно минусование кармы в этом помогает, но не всегда.


                                                        1. PsyHaSTe
                                                          02.07.2018 00:09

                                                          1. А что насчет времен жизни? Например если мы получаем ссылку и записываем её адрес какой-нибудь глобальной переменной? Я к тому, что время жизни этой ссылки может быть нестатическим.
                                                          2. Ну я выше использовал сырой указатель, каковой и должен использоваться в таком случае (если верить цитате ниже)
                                                            В C++ в подобном примере кода не будет ни unique_ptr, ни shared_ptr. Контроля со стороны компилятора так же не будет, но ведь речь про unique_ptr/shared_ptr.

                                                          Ну и вопрос в том, что в примере ( https://habr.com/post/415737/#comment_18835267 ) возвращается шарящаяся ссылка, но она все равно нарушает гарантии, можно случайно сделать видимое изменение в коде, который получил const ptr.


                                                          1. alexeykuzmin0
                                                            02.07.2018 00:47

                                                            1. Тогда нам нужно решить, будет этот указатель владеющим или нет. Если будет — shared_ptr, поскольку у нас ситуация разделения владения, если нет — обычный указатель или обертки над ним.
                                                            2. Если вы хотите передать владение объектом в функцию, то нужно его передать. Поскольку разделяемое владение нам все еще не нужно, это std::unique_ptr. Тогда код будет выглядеть следующим образом:

                                                            template<typename T>
                                                            unique_ptr<T> print(unique_ptr<T> value) {
                                                                cout << *value;
                                                                return value;
                                                            }
                                                            ...
                                                            auto a = make_unique<string>("123");
                                                            *a = "456";
                                                            auto b = print(move(a));
                                                            *a = "789"; // Здесь статический анализ найдет использование объекта после move. Если статический анализ не запускать, то будет UB, за исключением некоторых типов
                                                            auto c = print(move(b));


                                                            Последнюю фразу не понял. Код, который получил указатель на константную память (не путать с константным указателем!) не может ее изменить. А чтобы получить обычный указатель из указателя на константную память, нужен const_cast. Другой кусок кода может получить указатель на тот же самый кусок памяти как на не-константный, тогда он сможет его менять.


                                                            1. PsyHaSTe
                                                              02.07.2018 11:58

                                                              Собственно тут везде речь про один владеющий указатель в main, все остальные — разделяемые. Поэтому


                                                              1. Получается, нужно пользоваться сырыми указателями. Это очень неприятно и error-prone.
                                                              2. Нет, я не хочу передать владение. Есть мой ptr которым я владею, и я хочу дать им попользоваться. Ну например передал в поток, который каждую секунду выводит это сообщение в stdout. И после этого мутировать переданную переменную это ошибка. Можно от этого как-то защититься, не передавая владение?

                                                              Последнюю фразу не понял. Код, который получил указатель на константную память (не путать с константным указателем!) не может ее изменить. А чтобы получить обычный указатель из указателя на константную память, нужен const_cast. Другой кусок кода может получить указатель на тот же самый кусок памяти как на не-константный, тогда он сможет его менять.

                                                              Да, понял, что немного не так выразился. Тут просто еще вопрос скоупинга. Если посмотреть пример ниже, скоуп первого вызова явно ограничен { ... }, поэтому мы после этого можем мутировать переменную. Второй вызов имеет скоупом весь main и после этого мы мутировать переменную уже не можем. То есть нам нужно поведение как const, так и mut не в сигнатуре функции, а в сценарях её использования.


                                        1. freecoder_xx
                                          01.07.2018 21:27
                                          +1

                                          Вот как будет выглядеть такой код на Rust:


                                          pub struct S<'a> {
                                              link: &'a  i32 // иммутабельная ссылка
                                          }
                                          
                                          struct S2<'a> {
                                              vec: Vec<S<'a>> // глобальный массив объектов, которые будут разделять владение переменной
                                          }
                                          
                                          impl<'a> S2<'a> {
                                              fn create_vector(&mut self) {
                                                  self.vec = Vec::new(); // создаем массив
                                                  let x = 170239; // переменная, на которую будем ссылаться
                                                  for _ in 0..10 {
                                                      let s = S {
                                                          link: &x
                                                      };
                                                      self.vec.push(s);
                                                  }
                                              }
                                          
                                              fn change_vector(&mut self) {
                                                  let x = 239017; // другая переменная, некоторые ссылки из массива мы обновим на нее
                                                  for _ in 0..20 {
                                                      // обновляем случайный элемент
                                                      let ind = rand::random::<usize>() % 10;
                                                      let s = S {
                                                          link: &x
                                                      };
                                                      self.vec[ind] = s;    
                                                  }
                                              }
                                          }

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


                                          И как вы может убедиться сами, несовпадение времен жизни ссылок приведут к ошибкам компиляции:


                                          error[E0597]: `x` does not live long enough
                                            --> src/main.rs:17:24
                                             |
                                          17 |                 link: &x
                                             |                        ^ borrowed value does not live long enough
                                          ...
                                          21 |     }
                                             |     - borrowed value only lives until here
                                             |
                                          note: borrowed value must be valid for the lifetime 'a as defined on the impl at 11:1...
                                            --> src/main.rs:11:1
                                             |
                                          11 | impl<'a> S2<'a> {
                                             | ^^^^^^^^^^^^^^^


                                          1. alexeykuzmin0
                                            01.07.2018 21:59
                                            +1

                                            Да, мне уже указали, что для разделяемого владения нужен Rc.


                      1. eao197
                        01.07.2018 14:36

                        Ну я за всю Одессу, конечно, не скажу, но «синтаксис подлиннее» для такой базовой вещи, как ссылка — как мне кажется, очень важно.
                        Вас может удивить, но в C++ такая базовая вещи, как ссылка, имеет очень компактный синтаксис. А unique_ptr/shared_ptr — это вовсе не замена ссылкам.
                        А в том же расте компилятор не ведет подсчет ссылок, а просто генерирует drop(x) в некотором месте, где он вывел смерть переменной с учетом всего этого.
                        Представляете, в C++ точно так же!
                        vector<MyObject> make_vector() {...}
                        void f() {
                          vector<MyObject> objects = make_vector();
                          ...
                        } // Здесь компилятор вызовет деструктор для objects без какого-либо подсчета ссылок.
                        

                        Есть ощущение, что вы просто не знаете, где и для чего в C++ используются умные указатели вообще и unique_ptr/shared_ptr в частности.


                        1. PsyHaSTe
                          01.07.2018 14:43
                          +1

                          И кто помешает вернуть указатель на objects который будет удален в конце функции? Если мы юзаем умные указатели, то они проконтролируют этот момент. а без них как? Раз я «не понимаю», то может, разъясните?


                          1. eao197
                            01.07.2018 14:51

                            У вас есть функция:

                            vector<MyObject> make_vector() {...}
                            Зачем вам возвращать указатель?


                            1. PsyHaSTe
                              01.07.2018 15:57

                              Что мне помешает написать такой код?


                              MyObject* f() {
                                vector<MyObject> objects = make_vector();
                                return objects[0];
                              }


                              1. eao197
                                01.07.2018 16:05
                                -2

                                В принципе, ничего. Но этот код не имеет отношения к предназначению и использованию unique_ptr/shared_ptr.


                              1. Antervis
                                01.07.2018 16:55

                                здравый смысл. В расте можно написать такую же белиберду в unsafe блоке, но почему-то же никто (надеюсь) так не делает?

                                The problem is not the problem. The problem is your attitude about the problem. Do you understand? © Jack Sparrow


                                1. PsyHaSTe
                                  01.07.2018 17:28
                                  +1

                                  Здравый смысл это да, но тот же пример статей PVS studio говорит, что программисты ошибаются. Тот же эффект последней строки тоже казалось бы невозможен, ведь «здравый смысл» подсказывает, как надо было написать. Но это происходит на практике.

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


                                  1. eao197
                                    01.07.2018 17:54
                                    -2

                                    PsyHaSTe, так а что с темой unique_ptr? Ваш пример здесь не в тему, ибо в C++, как и в Rust-е, вы не можете возвращать ссылку/указатель на член локального вектора. Только в C++ вам за это по рукам никто не бьет, а в Rust-е вам на это компилятор укажет.

                                    Но фокус-то в том, что unqiue_ptr здесь вообще не при чем. Если же вы считаете, что при чем, то покажите, как вы собираетесь решить эту проблему с помощью unique_ptr в C++.

                                    Или вы опять ограничитесь минусами, а сами сольетесь в очередной раз?


                                    1. PsyHaSTe
                                      01.07.2018 18:15

                                      При чем тут unique_ptr? У меня к нему изначально вопросов не было, у меня был вопрос про shared.

                                      Только в C++ вам за это по рукам никто не бьет, а в Rust-е вам на это компилятор укажет.

                                      Ну в этом собственно и суть, чтобы компилятор дал по рукам. Если дает — хороший, годный компилятор, если нет — то не очень.

                                      Или вы опять ограничитесь минусами, а сами сольетесь в очередной раз?

                                      Я, конечно, дико извиняюсь, но я вообще минусами очень редко пользуюсь, даже в комментариях. Полагаю, что читателям просто нравится переходы на личности и наезды в дворовом стиле «а ты не бот ли часом?»/«чет опять слился»/etc
                                      image


                                      1. eao197
                                        01.07.2018 18:27
                                        -1

                                        При чем тут unique_ptr? У меня к нему изначально вопросов не было, у меня был вопрос про shared.
                                        Во-первых, есть ощущение, что вы не понимаете ни предназначение unique_ptr, ни shared_ptr. Поэтому для упрощения разговора попытался с вами поговорить хотя бы про unique_ptr.

                                        Во-вторых, вы не даете себе труда отвечать на вопросы и уточнять те ваши туманные высказывания, которые не понятны собеседнику. В частности, это ведь вы написали:
                                        Видимо разница в том, что у вас семантика как у unique_ptr и shared_ptr, только из коробки. В С++ выбор или забить и использовать сырые указатели, или везде иметь кучу этих самых unique_ptr, что не очень удобно.
                                        Не сочтите за труд, объясните, о чем вы здесь пытались сказать. Ибо не понятно от слова совсем.

                                        В-третьих, вы указали на «проблемный код»:
                                        MyObject* f() {
                                          vector<MyObject> objects = make_vector();
                                          return objects[0];
                                        }

                                        Покажите, как вы хотите сделать его «нормальным» с помощью unique_ptr/shared_ptr. И аналог из Rust-а. Чтобы можно было предметно показать вам, в чем вы заблуждаетесь.

                                        Отрадно, что это не вы решили отвечать минусами. Плохо то, что вы сливаетесь и не отвечаете.


                                        1. PsyHaSTe
                                          01.07.2018 18:53

                                          Во-первых, есть ощущение, что вы не понимаете ни предназначение unique_ptr, ни shared_ptr. Поэтому для упрощения разговора попытался с вами поговорить хотя бы про unique_ptr.

                                          Я говорю с позиции, что unique ptr — это уникальный указатель, который может быть только один, и передает владение при передаче, а shared_ptr, соответственно, позволяет раздавать равноправный доступ. Могу ошибаться, я в последние стандарты плюсов не лез, для меня в свое время откровением было появление лямбд и auto.


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

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


                                          Отрадно, что это не вы решили отвечать минусами. Плохо то, что вы сливаетесь и не отвечаете.

                                          Знаете, я устал уже отвечать на важе "че слился" в десятый раз. Всего доброго, честь имею.


                                          1. eao197
                                            01.07.2018 18:59

                                            Знаете, я устал уже отвечать на важе «че слился» в десятый раз. Всего доброго, честь имею.
                                            По факту, вы не сказали ничего, что могло бы прояснить фразу: «и вы почувствуете, что unique_ptr и shared_ptr (которые сами по себе были глотком свежего воздуха), выглядят как неудачная шутка» Хотя именно в ее обсуждение вы и попытались влезть.

                                            В сухом остатке: вы не смогли рассказать, в чем отличия unique_ptr/shared_ptr от Box/Rc/Arc. И, уж тем более, не смогли показать, чем unique_ptr/shared_ptr хуже.

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

                                            Что же это тогда, как не слив? Как бы вы к этому высказыванию не относились.


                                            1. PsyHaSTe
                                              01.07.2018 19:21
                                              +1

                                              В сухом остатке: вы не смогли рассказать, в чем отличия unique_ptr/shared_ptr от Box/Rc/Arc. И, уж тем более, не смогли показать, чем unique_ptr/shared_ptr хуже.

                                              Постараюсь последний раз объяснить.

                                              shared_ptr я так понимаю позволяет шарить стейт с Rc — ОК.

                                              голый указатель позволяет не иметь оверхеда — ОК.

                                              Проблема в том, что если мы хотим разделяемый неизменяемый стейт, у нас есть два механизма — надежный, но с рантайм оверхедом, и ненадежный без оверхеда.

                                              С другой стороны, имея неизменяемые ссылки (а так же отсутствие алиасинга и прочие приятные штуки) мы можем статически определить место, где новых ссылок больше не появляется, и дропать значение в этом месте. Имеем лучшее из двух миров. Если у нас значение изменяемое, то конечно же так сделать нельзя. Тут еще играет факт readonly by default в расте.

                                              Rc/Arc это тот же shared_ptr с рантайм оверхедом, никакой магии там нет. Разница лишь в том, что они нужны в очень малом проценте задачи и/или ситуаций. В остальном отлично справляются языковые ссылки `&` без оверхеда.

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


                                              1. eao197
                                                01.07.2018 19:35
                                                -3

                                                Постараюсь последний раз объяснить.
                                                Вы в очередной раз не смогли. Вопрос был вполне конкретный: чем unique_ptr/shared_ptr хуже Box/Rc/Arc. Ответа нет.

                                                Рассуждения о том, что вы можете что-то дропать, во-первых, хорошо было бы подтверждать примерами кода. Ибо верить вам на слово поводов нет. И, во-вторых, рассуждения эти не имеют отношения к тому, что меня лично интересовало.


                                                1. PsyHaSTe
                                                  01.07.2018 19:45
                                                  +1

                                                  Вы в очередной раз не смогли. Вопрос был вполне конкретный: чем unique_ptr/shared_ptr хуже Box/Rc/Arc. Ответа нет.

                                                  Я выше на пальцах объяснил хотя бы тот факт, что Box не имеет отношения к рассматриваемому вопросу. Точно так же вы видимо полностью проигнрировали тот факт, что Rc это и есть растовый shared_ptr. Наконец, вопрос «лучшести»/«хужести», поднятый автором, я так понимаю, сводится к тому, что shared_ptr является оверкиллом во сногих случаях, когда можно обойтись просто передачей ссылки без подсчета.

                                                  Ибо верить вам на слово поводов нет.

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


                                                  1. eao197
                                                    01.07.2018 20:27

                                                    Я выше на пальцах объяснил хотя бы тот факт, что Box не имеет отношения к рассматриваемому вопросу.
                                                    Нет. Либо вы опять о чем-то своем. Не вижу отличий Rust-овского Box-а от плюсового unique_ptr.
                                                    Точно так же вы видимо полностью проигнрировали тот факт, что Rc это и есть растовый shared_ptr.
                                                    Вы ошибаетесь. Я в курсе, что такое Rc и Arc, чем они похожи, а чем отличаются от shared_ptr.

                                                    И, зная это, мне сложно понять, чем shared_ptr хуже Rc/Arc.
                                                    Наконец, вопрос «лучшести»/«хужести», поднятый автором, я так понимаю, сводится к тому, что shared_ptr является оверкиллом во сногих случаях, когда можно обойтись просто передачей ссылки без подсчета.
                                                    Так вот я вам в очередной раз и говорю: вы не понимаете. А чтобы говорить с вами предметно, нужно оперировать примерами кода. Коих вы привести не можете.
                                                    Мы не в церкви, чтобы верить, чего я и не прошу, собственно.
                                                    Достаточно привести пример кода, в котором компилятор Rust-а вывел бы что-то без подсчета ссылок. Но пока примеры кода не по вашей части, увы.


                                                    1. PsyHaSTe
                                                      01.07.2018 23:10

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

                                                      Ок, вот пример кода:


                                                      use std::fmt::Display;
                                                      
                                                      fn print_and_get_reference<T: Display>(value: &T) -> &T {
                                                          println!("{}", value);
                                                          &value
                                                      }
                                                      
                                                      fn main() {
                                                          let mut test_value = "123";
                                                          {
                                                              let _foo = print_and_get_reference(&test_value);
                                                          }
                                                          test_value = "456";
                                                          let _bar = print_and_get_reference(&test_value);
                                                          test_value = "789"; // cannot assign to `test_value` because it is borrowed
                                                          let _baz = print_and_get_reference(&test_value);
                                                      }

                                                      Тут происходит проверка референсов и оказывается, что мы чуть не поменяли значение, которое менять мы не хотели.


                                                      Достаточно привести пример кода, в котором компилятор Rust-а вывел бы что-то без подсчета ссылок. Но пока примеры кода не по вашей части, увы.

                                                      Я уже немного утомился от постоянных переходов на личности. Может хватит подобной низкопробной демагогии? Она вас не красит.


                                                      1. eao197
                                                        01.07.2018 23:18

                                                        Тут происходит проверка референсов и оказывается, что мы чуть не поменяли значение, которое менять мы не хотели.
                                                        Послушайте, ну ведь это же не имеет отношения к тому, для чего применяются unique_ptr/shared_ptr в C++ (см. здесь). Так что к ответу на вопрос о том, почему unique_ptr/shared_ptr можно считать плохой шуткой, мы не приближаемся. В C++ в подобном примере кода не будет ни unique_ptr, ни shared_ptr. Контроля со стороны компилятора так же не будет, но ведь речь про unique_ptr/shared_ptr.
                                                        Может хватит подобной низкопробной демагогии?
                                                        Может надо было сразу переходить к примерам?


                                                        1. PsyHaSTe
                                                          01.07.2018 23:33

                                                          В C++ в подобном примере кода не будет ни unique_ptr, ни shared_ptr. Контроля со стороны компилятора так же не будет, но ведь речь про unique_ptr/shared_ptr.

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


                                                          1. eao197
                                                            01.07.2018 23:39

                                                            Мы говорили о том, как нам в С++ железно получит подобный контроль
                                                            Так вы и не получите в C++ подобный контроль. Ни с умными указателями, ни без. Или вы сможете?
                                                            Мы не говорили «а давайте просто сырые указатели передавать и пусть программист сам контролирует время жизни».
                                                            Простите, но я вообще не помню, такой темы в нашем разговоре. Дайте ссылку где она всплыла.

                                                            На всякий случай: в современном C++ голый указатель рассматривается как невладеющий. Т.е. дергать для него delete не рекомендуется. Предполагается, что время жизни контролирует тот, кто предоставил вам голый указатель.


                                                            1. PsyHaSTe
                                                              02.07.2018 00:11

                                                              Так вы и не получите в C++ подобный контроль. Ни с умными указателями, ни без. Или вы сможете?

                                                              Разве повсеместное использование shared_ptr для ВСЕХ указателей не даст подобного контроля?


                                                              На всякий случай: в современном C++ голый указатель рассматривается как невладеющий. Т.е. дергать для него delete не рекомендуется. Предполагается, что время жизни контролирует тот, кто предоставил вам голый указатель.

                                                              благодарю, буду иметь в виду.


                                                              1. eao197
                                                                02.07.2018 00:24

                                                                Разве повсеместное использование shared_ptr для ВСЕХ указателей не даст подобного контроля?
                                                                Такого контроля, как вы показали выше, когда компилятор бьет по рукам за изменение объекта, на который кто-то уже ссылается, — нет. Более того, в C++ никто вам не запретит сделать так:
                                                                auto sptr1 = std::make_shared<int>(42);
                                                                auto sptr2 = sptr1;
                                                                delete sptr1.get();
                                                                *sptr2 = 42;
                                                                
                                                                Ошибку вы получите только в run-time. И то, если вам повезет упасть сразу, а не тихо запортить память.


                                                                1. PsyHaSTe
                                                                  02.07.2018 11:58

                                                                  Понятно, спасибо.


                                                                  1. eao197
                                                                    02.07.2018 12:02
                                                                    +1

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

                                                                    К сожалению, местами shared_ptr используют неуместно. Это затрудняет разговор о достоинствах и недостатках unique_ptr/shared_ptr в C++.


                                                1. nexmean
                                                  01.07.2018 19:56
                                                  +2

                                                  И, во-вторых, рассуждения эти не имеют отношения к тому, что меня лично интересовало.

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


                                                  1. eao197
                                                    01.07.2018 20:31
                                                    -1

                                                    Ничего подобного. Речь изначально шла о том, что unique_ptr/shared_ptr объявлялись ущербными по сравнению с тем, что есть в Rust-е. В Rust-е из аналогичных средств есть Box/Rc/Arc. И пока никто из растоманов не смог объяснить, чем одно лучше/хуже другого.

                                                    Растоманы пытаются перевести стрелки на контроль времени жизни, который жестко контролируется в Rust-е. Но, видимо, из-за недостатка опыта в C++, не понимают, для чего в C++ используются unique_ptr/shared_ptr. А из-за этого непонимания с растоманами не получается вести конструктивный диалог.


                                                    1. humbug
                                                      01.07.2018 20:39

                                                      из-за недостатка опыта в C++

                                                      Видимо у вас из-за недостатка знаний Rust не получается понять.


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


                                                      1. eao197
                                                        01.07.2018 20:44

                                                        Расскажите, пожалуйста, что же именно я не понимаю. Это не сарказм, это действительно не понимание того, почему диалог с растоманами не складывается.


                                                        1. humbug
                                                          01.07.2018 20:49

                                                          что же именно я не понимаю

                                                          Да практически все достижения человечества за вычетом жалких 0.000001%. И то я сомневаюсь, что вы способны осознать подобные объемы информации.


                                                          Вы сами должны определиться, чего вы не понимаете. Я ж не психолог.


                                                          1. eao197
                                                            01.07.2018 21:04
                                                            +1

                                                            От ведь.

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

                                                            В C++ нормальное применение unique_ptr/shared_ptr — это управление жизнью динамически созданными объектами. По каким-то причинам объект пришлось создать в хипе и хочется избежать ручного управления временем его жизни за счет плюсовых конструкторов/деструкторов. По каким причинам пришлось создавать объект в хипе — не суть важно. Скорее всего, объект просто должен пережить скоуп, в котором создан. Хотя здесь может быть все веселее за счет фабрик и прочих ООП-ных заморочек, но это уже ненужные детали.

                                                            Ключевой момент в том, что нормальное применение unique_ptr/shared_ptr — это контроль жизни динамически созданного объекта. Unique_ptr для простых случаев, когда владелец один. Shared_ptr для более сложных ситуаций, когда владельцев несколько и нет возможности статически разрулить время владения.

                                                            Так вот, насколько я понимаю, в Rust-е с использованием Box/Rc/Arc все в точности тоже самое. За исключением деталей вроде отсутствия атомарного счетчика ссылок в Rc.

                                                            Ненормальное использование shared_ptr в C++ (именно shared_ptr, ибо со случаями подобного использования unique_ptr сталкиваться не доводилось) — это когда людям лень разбираться со временем владения объекта и они тупо заворачивают объект в shared_ptr. Особенно часто такое встречается с людьми, которые пришли в C++ из языков с GC. Вот в их коде запросто можно увидеть что-то вроде:

                                                            std::shared_ptr<std::vector<MyObject>> make_vector() {...};

                                                            или даже:
                                                            std::shared_ptr<std::vector<std::shared_ptr<MyObject>> make_vector() {...}

                                                            Хотя вполне можно было бы обойтись самым простым:
                                                            std::vector<MyObject> make_vector() {...}

                                                            Так вот у меня есть ощущение, что оппонирующие мне растоманы пытаются перевести разговор именно на подобное использование shared_ptr. Мол, там, где в C++ люди для простоты берутся за shared_ptr, в Rust-е можно полагаться на контроль времени жизни объектов.

                                                            Итак: правильно ли я понимаю то, о чем пытаются здесь говорить растоманы?


                                                            1. humbug
                                                              01.07.2018 21:40
                                                              +3

                                                              Ключевой момент в том, что нормальное применение unique_ptr/shared_ptr — это контроль жизни динамически созданного объекта.

                                                              Отвечаю как программист с опытом С++: на самом деле это не так. Это умные указатели. Они не контролируют время жизни, по Александреску они созданы для дереференса и для вызова правильного деструктора либо после разрушения переменной(unique_ptr), либо по достижению счетчика нуля(shared_ptr). Ну и в какой-то степени unique_ptr можно считать частным случаем shared_ptr.


                                                              Вопрос: почему они не контролируют время жизни объекта? Потому что RAII хорош только в том случае, когда ты используешь так называемые "правила хорошего тона". Вот как можно сломать unique_ptr:


                                                              #include <memory>
                                                              
                                                              int main () {
                                                                int* p = new int (10);
                                                                std::unique_ptr<int> a (p);
                                                                std::unique_ptr<int> b (p);
                                                              }

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


                                                              Отвечаю как программист с опытом Rust: время жизни объектов контролирует сам язык, поэтому вышеуказанная ситуация в Rust в принципе не возможна. К примеру, Box используется для размещения объекта в куче (и вызове free при разрушении; почти как unique_ptr):


                                                              fn main() {
                                                                  let p = String::from("10");
                                                                  let a = Box::new(p);
                                                                  let b = Box::new(p);
                                                              }

                                                              Программа не соберется с ошибкой:


                                                              error[E0382]: use of moved value: `p`
                                                               --> src/main.rs:4:22
                                                                |
                                                              3 |     let a = Box::new(p);
                                                                |                      - value moved here
                                                              4 |     let b = Box::new(p);
                                                                |                      ^ value used here after move
                                                                |

                                                              Вот что такое контроль времени жизни объекта. К Box/Rc/Arc это не имеет никакого отношения. Это вшито в язык, а не в библиотечный код.


                                                              И опять не понял. В Rust-е, afaik, типы Box/Rc/Arc так же являются частью стандартной библиотеки языка. И используются для тех же самых целей. Так почему же «и вы почувствуете, что unique_ptr и shared_ptr (которые сами по себе были глотком свежего воздуха), выглядят как неудачная шутка»?

                                                              Теперь можете сами попробовать почувствовать, шутка или нет.


                                                              1. eao197
                                                                01.07.2018 21:53
                                                                -1

                                                                Это умные указатели. Они не контролируют время жизни, по Александреску они созданы для дереференса и для вызова правильного деструктора либо после разрушения переменной(unique_ptr), либо по достижению счетчика нуля(shared_ptr).
                                                                Приплыли. Умные указатели, которые не контролируют время жизни, но вызывают деструктор…


                                                                1. humbug
                                                                  02.07.2018 03:47

                                                                  Умные указатели, которые не контролируют время жизни, но вызывают деструктор…

                                                                  А что по-вашему есть время жизни?


                                                                  1. eao197
                                                                    02.07.2018 07:09

                                                                    А что по-вашему есть время жизни?
                                                                    В С++ — это время между инициализацией (вызовом конструктора) и деинициализацией (завершением деструктора) объекта. Для статических, автоматических объектов и членов агрегатов (C-шных массивов, членов структур/классов) это время контролируется языком (компилятором/ран-таймом). Для объектов, созданных через new (обычный, перегруженный или placement new), время жизни контролирует пользователь. Умные указатели как раз созданы для упрощения этого контроля.

                                                                    Ну а ваш Rust-овский пример, как и ряд других примеров, упомянутых в обсуждении unique_ptr/shared_ptr vs Box/Rc/Arc, нерелевантен, поскольку он показывает общие свойства языка Rust. Вы можете убрать оттуда Box и все равно будет продолжать показывать те же самые свойства:
                                                                    fn take_and_show(w: String) {
                                                                        println!("value: {}", w);
                                                                    }
                                                                    fn main() {
                                                                        let p = String::from("Bla-bla-bla");
                                                                        take_and_show(p);
                                                                        take_and_show(p);
                                                                    }

                                                                    Между тем, вопрос, который меня интересует, состоит именно в том, почему unique_ptr/shared_ptr объявлены неудачной шуткой.


                                                                    1. humbug
                                                                      02.07.2018 08:35
                                                                      +1

                                                                      В С++ — это время между инициализацией (вызовом конструктора) и деинициализацией (завершением деструктора) объекта.

                                                                      И как же умные указатели могут контролировать время жизни? :D


                                                                      1. eao197
                                                                        02.07.2018 08:37

                                                                        Вызывают деструктор подконтрольного им объекта в подходящее время (в собственном деструкторе в случае unique_ptr, при обнулении счетчика сильных ссылок в случае shared_ptr).


                                                                        1. humbug
                                                                          02.07.2018 08:40

                                                                          Вызывают деструктор подконтрольного им объекта в подходящее время

                                                                          Ну вот в этом и проблема. Вы можете вызвать деструктор, а переменная еще будет существовать, что может привести ко всяким веселым последствиям. Какой же это контроль?


                                                                          Контролировать время жизни может лишь компилятор. А он в случае С++ забывает это делать.


                                                                          1. eao197
                                                                            02.07.2018 08:42

                                                                            Какой же это контроль?
                                                                            Обычный для C++.


                                                                            1. humbug
                                                                              02.07.2018 08:44
                                                                              +4

                                                                              Ну так и скажите, что это дырявый контроль, если у тебя умерла переменная на руках, а ты этого не узнаешь и с этим ничего сделать не можешь. Ой, можешь совершить UB, можешь кинуть сигнал и упасть в рантайме))) Мощный выбор.


                                                                              1. eao197
                                                                                02.07.2018 08:54

                                                                                Ну так и скажите, что это дырявый контроль,
                                                                                Простите, но разве где-то утверждалось обратное?

                                                                                Вы упорно уводите разговор в сторону. Посему напомню еще раз вопрос, на который я хотел бы получить ответ, но не могу: почему unique_ptr/shared_ptr являются неудачной шуткой?

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

                                                                                Только вот ссылки Rust-а, borrow checker, lifetimes и пр. следует противопоставлять ссылкам/указателям в C++ и отсутствию этих самых borrow checker-ов, lifetimes и пр. в C++.

                                                                                Тогда как unique_ptr/shared_ptr в C++ появляются в особых случаях (о которых речь уже была) и служат вполне конкретным целям. Таким же, насколько я могу судить, как и Box/Rc/Arc в Rust-е. Так в чем ущебность unique_ptr/shared_ptr?

                                                                                Такое ощущение, что лишь тем, что unique_ptr/shared_ptr в С++.


                                                                                1. humbug
                                                                                  02.07.2018 09:12
                                                                                  +1

                                                                                  А вот если сравнивать сами unique_ptr и Box, то чем один лучше другого?

                                                                                  Я понимаю ваш вопрос, особенно в контексте:


                                                                                  Между тем, вопрос, который меня интересует, состоит именно в том, почему unique_ptr/shared_ptr объявлены неудачной шуткой.

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


                                                                                  В C++ все небезопасно, это его идеология.

                                                                                  То получается, что unique_ptr/shared_ptr ни за что и не отвечают. В книгах их преподносят как панацею(детишки, не трогайте new/malloc), а на деле они не контролируют время жизни, они периодически вызывают деструкторы, а ты должен молиться всем богам, чтобы у тебя не было живых ссылок на объект в момент вызова деструктора.


                                                                                  Тогда как unique_ptr/shared_ptr в C++ появляются в особых случаях (о которых речь уже была) и служат вполне конкретным целям.

                                                                                  Простите, каким конкретным целям? Я что-то не видел этого в дискуссии. Либо это было наброшено вами без аргументов.


                                                                                  Выдержка из Александреску(Современное проектирование на С++, глава 7 "Интеллектуальные указатели", стр 179):


                                                                                  Интеллектуальные указатели — это объекты языка С++, имитирующие обычные указатели с помощью реализации оператора -> и унарного оператора *. Кроме того, они часто скрытно выполняют полезные задания (например, осуществляют управление памятью или блокировку)

                                                                                  А теперь сравните это с вашим оскорбительным тоном:


                                                                                  Приплыли. Умные указатели, которые не контролируют время жизни, но вызывают деструктор…

                                                                                  Учите матчасть, прежде чем спорить.


                                                                                  1. eao197
                                                                                    02.07.2018 09:20

                                                                                    То получается, что unique_ptr/shared_ptr ни за что и не отвечают.
                                                                                    Вы путаете. Они отвечают, но с гарантиями у них так же, как и во всем остальном C++.
                                                                                    В книгах их преподносят как панацею(детишки, не трогайте new/malloc)
                                                                                    Полагаю, здесь имеет место случай, когда вы прочитали невнимательно и проинтерпритировали по своему.
                                                                                    Я что-то не видел этого в дискуссии.
                                                                                    Я был бы признателен, если бы вы читали то, что пишут именно вам. Особенно, если бы вы читали это внимательно.

                                                                                    Ссылки на Александреску не котируются, ибо:

                                                                                    1. Умные указатели появились и начали использоваться задолго до Modern C++ Design.

                                                                                    2. Есть ненулевая вероятность, что вы интерпритируете слова по-своему и выдергиваете их из контекста.


                                                                                    1. humbug
                                                                                      02.07.2018 09:33

                                                                                      Расскажите, пожалуйста, что же именно я не понимаю. Это не сарказм, это действительно не понимание того, почему диалог с растоманами не складывается.

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


                                                                                      1. eao197
                                                                                        02.07.2018 09:36

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

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


                                                                                        1. humbug
                                                                                          02.07.2018 09:46
                                                                                          +1

                                                                                          И не временем жизни переменной, а моментом вызова deleter-а для подконтрольного указателя (значения в общем случае). Время жизни переменных контролирует компилятор.

                                                                                          Я рад, что вы постепенно признаете свои ошибки.


                                                                                          Попробуйте показать, как с помощью unique_ptr/shared_ptr сделать что-то кроме управления временем жизни ресурса

                                                                                          Лихко. Это даже забавно, как Дима Шарихин приводит в плюсы недостатки проектирования, которыми я воспользуюсь:


                                                                                          template<typename T>
                                                                                          using deleted_unique_ptr = std::unique_ptr<T,std::function<void(T*)>>;
                                                                                          
                                                                                          deleted_unique_ptr<FILE> file(
                                                                                              fopen("file.txt", "r"),
                                                                                              [](FILE*) { printf("Fuck you"); });

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


                                                                                          1. eao197
                                                                                            02.07.2018 09:48

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


                                                                                            1. humbug
                                                                                              02.07.2018 09:50

                                                                                              И управление им происходит по той же самой схеме, что и разрушение динамически созданных объектов.

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


                                                                                              1. eao197
                                                                                                02.07.2018 10:14

                                                                                                В вышеуказанном коде управления не происходит.
                                                                                                Происходит. Вы пообещали, что печать фразы «F**k you» освободит ресурс. Компилятор и stdlib вам поверили. То, что вы пообещали ерунду никак не влияет на то, что unique_ptr честно попытался ресурс освободить.

                                                                                                Тем не менее, похоже, что упражнения в утонченном хамстве — это все, что вы можете сказать по исходному вопросу.


                                                                                                1. humbug
                                                                                                  02.07.2018 10:23

                                                                                                  утонченном хамстве

                                                                                                  Спасибо, я знал, что вы оцените)))


                                                                                                  И не временем жизни переменной, а моментом вызова deleter-а для подконтрольного указателя (значения в общем случае). Время жизни переменных контролирует компилятор.

                                                                                                  Ну вот мы с вами и сошлись во мнении. И unique_ptr/shared_ptr, и Box/Rc/Arc — это все одно и то же. Разница в языках. И как показывает практика и вышеуказанные примеры, разница не в пользу С++.


                                                                                                  1. eao197
                                                                                                    02.07.2018 10:29

                                                                                                    И unique_ptr/shared_ptr, и Box/Rc/Arc — это все одно и то же.
                                                                                                    Таким образом фраза «Поиграйте немного с Rust, и вы почувствуете, что unique_ptr и shared_ptr (которые сами по себе были глотком свежего воздуха), выглядят как неудачная шутка.» остается необъясненной. Ибо что неудачного в unique_ptr/shared_ptr, если тоже самое пришлось сделать в Rust-е, непонятно.

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


                                                                                                    1. humbug
                                                                                                      02.07.2018 10:41
                                                                                                      +1

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

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


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


                                                                                                      1. eao197
                                                                                                        02.07.2018 10:45

                                                                                                        и наполнил вашу голову новыми знаниями
                                                                                                        Ничего нового вы мне здесь не открыли.
                                                                                                        И получил за это Ушат Помоев
                                                                                                        Ваша мнительность может дать фору вашей дерзости.


                                                                                                        1. humbug
                                                                                                          02.07.2018 10:54

                                                                                                          Ничего нового вы мне здесь не открыли.

                                                                                                          То есть вы на пустом месте раздуваете конфликт, устраиваете демагогию? Это неприлично.


                                                                                                          Если вы все же не являетесь троллем, прошу в gitter.


                                                                                                          1. eao197
                                                                                                            02.07.2018 10:57

                                                                                                            То есть вы на пустом месте раздуваете конфликт, устраиваете демагогию?
                                                                                                            Скорее всего, вы просто не понимаете сути моего вопроса.
                                                                                                            прошу в gitter
                                                                                                            Зачем? Я прекрасно знаю, в чем Rust лучше C++, знаю условия, при которых я предпочту один язык другому. И эти знания не позволяют мне понять претензии к unique_ptr/shared_ptr, высказанные в статье. Вряд ли вы сможете объяснить мне суть этих претензий.


                                                                                                            1. humbug
                                                                                                              02.07.2018 11:11

                                                                                                              Я вам первым примером показал проблемы умных указателей в С++. Они не владеют объектами и не управляют временем жизни. Они держат указатель и в нужный момент вызывают делитер. Поэтому можно создать 2 умных указателя на один ресурс и получить выстрел в ногу. Или с помощью метода функции-члена ::get получить сырой указатель, который окажется у вас на руках после смерти умного указателя.


                                                                                                              Вам мало минусов? Вы их упорно не хотите замечать?


                                                                                                              1. eao197
                                                                                                                02.07.2018 11:21

                                                                                                                Вам мало минусов? Вы их упорно не хотите замечать?
                                                                                                                Это не минусы конкретно unique_ptr/shared_ptr. Это принципиальные особенности C++ — компилятор верит, что вы знаете, что вы делаете. Если вы хотите засунуть один и тот же указатель в два unique_ptr, то значит вам это нужно. Равно и как использовать возвращаемое значение get() в качестве владеющего указателя. А если вам этого не нужно, то, вероятно, вам не нужен и сам C++.

                                                                                                                Так что все эти претензии мимо собственно умных указателей из C++ной stdlib.

                                                                                                                Раз вы не понимаете сути моего вопроса, то постараюсь объяснить на пальцах: между C++ и Rust-ом овердофига различий. И зная хотя бы часть из них, можно было бы сформулировать мысль про «неудачную шутку» так, чтобы она не вызывала вообще никаких вопросов. Вот с ходу пару-тройку примеров:

                                                                                                                «Поиграйте немного с Rust, и вы почувствуете, что enum class (который сам по себе были глотком свежего воздуха), выглядит как неудачная шутка.»

                                                                                                                «Поиграйте немного с Rust, и вы почувствуете, что std::variant и std::visit (которые сами по себе были глотком свежего воздуха), выглядят как неудачная шутка.»

                                                                                                                «Поиграйте немного с Rust, и вы почувствуете, что concepts lite (которые сами по себе были глотком свежего воздуха), выглядят как неудачная шутка.»

                                                                                                                «Поиграйте немного с Rust, и вы почувствуете, что ranges (которые сами по себе были глотком свежего воздуха), выглядят как неудачная шутка.»

                                                                                                                Ну и собственно, вопрос: почему при всем многообразии понятных и очевидных претензий, неудачной шуткой были признаны именно unique_ptr и shared_ptr?

                                                                                                                Ведь как раз unique_ptr/shared_ptr — это одно из самых удачных и вменяемых расширений C++ной stdlib. К ним вообще практически нет объективных претензий. За исключением, может быть, того, что в shared_ptr атомарные счетчики ссылок прибиты намертво гвоздями.


                                                                                                                1. humbug
                                                                                                                  02.07.2018 11:27

                                                                                                                  Это не минусы конкретно unique_ptr/shared_ptr. Это принципиальные особенности C++

                                                                                                                  Они написаны на C++. Вы постоянно об этом забываете.


                                                                                                                  1. eao197
                                                                                                                    02.07.2018 11:30
                                                                                                                    -1

                                                                                                                    Они написаны на C++.
                                                                                                                    Т.е. неудачность шутки в том, что классы для C++ной stdlib пишутся на C++. Да еще во времена, когда над зачаточной версией Rust-а работал один-единственный энтузиаст.

                                                                                                                    Действительно, штука так себе.


                                                                                                    1. Salabar
                                                                                                      02.07.2018 11:34
                                                                                                      +2

                                                                                                      a) В Rust они не могут быть NULL б) нельзя создать два независимых умных указателя на один объект.

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


                                                                                                      1. eao197
                                                                                                        02.07.2018 11:48

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

                                                                                                        По поводу NULL — это спорно. Нулевой указатель — это вполне себе нормальная вещь. А вот надобность от нее прятаться за Optional… Да еще в низкоуровневом языке…


                                                                                                        1. Salabar
                                                                                                          02.07.2018 12:08
                                                                                                          +2

                                                                                                          А вот надобность от нее прятаться за Optional… Да еще в низкоуровневом языке…


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


                                                                                                          1. eao197
                                                                                                            02.07.2018 12:11
                                                                                                            -1

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

                                                                                                            Так что для низкоуровневого кода спорно. Для высокоуровневого прикладного спорно применение C++ вообще.


                                                                                                            1. PsyHaSTe
                                                                                                              02.07.2018 12:19
                                                                                                              +1

                                                                                                              Почему не нужен-то? Если вы думаете, что раст таскает за собой теги Option'а и это приводит к лишнему оверхеду, то это не так, он даже сложные случаи вроде вложенных Either<T1,Either<T2,Either<T3,Either<T4,T5>>>> может обрабатывать. Зато на уровне типов сразу видно, где проверка необходима, а где её не должно быть


                                                                                                              1. eao197
                                                                                                                02.07.2018 12:23
                                                                                                                -1

                                                                                                                Потому, что вы не можете взять значение из Optional без проверки. Если можете, то смысл в Optional теряется. Вот, например, в плюсовом std::optional есть operator*, который проверку не делает. В этом случае никакой безопасности вы не получаете, сами несете за все ответственность. И есть метод value(), который проверку делает. Но это не бесплатно.

                                                                                                                Ну и речь не про оверхэд по данным, а оверхэд на проверку наличия данных при доступе к ним.


                                                                                                                1. PsyHaSTe
                                                                                                                  02.07.2018 12:34
                                                                                                                  +1

                                                                                                                  Потому, что вы не можете взять значение из Optional без проверки.

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


                                                                                                                  В этом случае никакой безопасности вы не получаете, сами несете за все ответственность.

                                                                                                                  Я не хочу никакой ответственности, я хочу, чтобы у компилятора болела голова за такие вещи, а я бизнес-логику писал.


                                                                                                                  Ну и речь не про оверхэд по данным, а оверхэд на проверку наличия данных при доступе к ним.

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


                                                                                                                  Ну вот вы пишете на расте:


                                                                                                                  fn foo(value: Option<Something>) {
                                                                                                                     if let Some(value) = value { 
                                                                                                                        value.bar();
                                                                                                                     }
                                                                                                                  }

                                                                                                                  ну ничем это не отличается от


                                                                                                                  void foo(Something* value) {
                                                                                                                     if (value) {
                                                                                                                        value.bar();
                                                                                                                     }
                                                                                                                  }


                                                                                                                  1. eao197
                                                                                                                    02.07.2018 12:44
                                                                                                                    +1

                                                                                                                    Я не хочу никакой ответственности, я хочу, чтобы у компилятора болела голова за такие вещи, а я бизнес-логику писал.
                                                                                                                    Тогда зачем вам C++ и что вы обсуждаете в теме про C++?
                                                                                                                    где вы сами бы их и написали
                                                                                                                    Простите, но если у вас есть:
                                                                                                                    fn some_low_level_code(o: Option<Box<Object>>) {...}

                                                                                                                    то вы не можете внутри взять содержимое o без проверки. Даже если вы знаете, что объект там есть. Следовательно, вы будете делать проверку в том или ином виде (match, unwrap или еще что-то). А если вы хотите этой проверки избежать, то вы где-то в одном месте сделаете проверку, возьмете ссылку и дальше будете оперировать только ссылкой.

                                                                                                                    Собственно, в C++ вы будете делать тоже самое. Просто у вас unique_ptr изначально будет чем-то вроде Option<Box<T>>.


                                                                                                                    1. PsyHaSTe
                                                                                                                      02.07.2018 12:54

                                                                                                                      Тогда зачем вам C++ и что вы обсуждаете в теме про C++?

                                                                                                                      Потому что у нас сравнительное обсуждение пользы от явного Option в аргументах, разве нет?


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

                                                                                                                      Если я знаю, что объект точно есть, то я напишу:


                                                                                                                      fn some_low_level_code(o: &Object) {...}

                                                                                                                      и никаких проверок писать не буду. Соответственно, дальнейшие рассуждения неверны.


                                                                                                                      И да, Box тоже почти не используется, не надо его писать в примеры :)


                                                                                                                      1. eao197
                                                                                                                        02.07.2018 12:57
                                                                                                                        +1

                                                                                                                        Потому что у нас сравнительное обсуждение пользы от явного Option в аргументах, разве нет?
                                                                                                                        Нет. Я надеюсь, мы все еще выясняем, почему unique_ptr/shared_ptr в С++ — это неудачная шутка.

                                                                                                                        Очередное объяснение — это nullability для unique_ptr/shared_ptr. Как по мне, это не баг, а фича. Т.к. нулевое значение для указателя — это его фундаментальное свойство в C++. Для тех, кто хочет иметь non_null указатели, есть gsl::not_null.


                                                                                                                        1. PsyHaSTe
                                                                                                                          02.07.2018 13:05

                                                                                                                          Нет. Я надеюсь, мы все еще выясняем, почему unique_ptr/shared_ptr в С++ — это неудачная шутка.

                                                                                                                          У меня предложение — как насчет написать автору и узнать у него? Потому как мне кажется, что он мог просто неправильно подать мысль, на что вы отвечаете «нет, он бы сказал тогда иначе». Стоит связаться с первоисточником, чтобы не плодить стены не очень полезного текста.

                                                                                                                          Очередное объяснение — это nullability для unique_ptr/shared_ptr. Как по мне, это не баг, а фича.


                                                                                                                          Я категорически не согласен с подобной позицией, но мне она ясна. Спасибо.


                                                                                                                          1. eao197
                                                                                                                            02.07.2018 13:09

                                                                                                                            Стоит связаться с первоисточником, чтобы не плодить стены не очень полезного текста.
                                                                                                                            ИМХО, это задача того, кто сюда притащил перевод :)


                                                                                                                            1. nexmean
                                                                                                                              02.07.2018 14:23
                                                                                                                              -1

                                                                                                                              ИМХО, это задача того, у кого возник такой вопрос и кому это интересно, то есть это твоя задача.


                                                                                                            1. nexmean
                                                                                                              02.07.2018 12:19
                                                                                                              +1

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

                                                                                                              Прикол Option в том, что вы в одном месте можете провереть его на None, достать из него значение, а потом пользоваться им как non-nullable указателем. Option нужен в низкоуровневом языке, чтобы избежать лишних проверок на null там, где они не нужны и не забыть их сделать там, где они нужны. Это можно сказать та самая защита от логических ошибок, которую даёт строгая статическая типизация.


                                                                                                              1. eao197
                                                                                                                02.07.2018 12:25

                                                                                                                Вы можете сделать тоже самое и с unique_ptr. Так что шило на мыло.


                                                                                                                1. nexmean
                                                                                                                  02.07.2018 12:36

                                                                                                                  Option<&T> не является владеющим указателем, в отличии от unique_ptr, более того, Option<&T> даже "умным" указателем не является, ибо оптимизируется в обычный сырой указатель и имеет значение только на этапе проверки схождения типов.


                                                                                                            1. humbug
                                                                                                              02.07.2018 12:22

                                                                                                              Вы в подавляющем большинстве случаев будете работать с указателем. Т.е. будете таскать именно указатель без проверок.

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


                                                                                                              1. eao197
                                                                                                                02.07.2018 12:25

                                                                                                                Почему-то вы считаете, что в низкоуровневом языке высокоуровневые абстракции не нужны. NonNull не нужен.
                                                                                                                Давайте вы не будете выдавать свои фантазии за мои слова, OK?


                                                                                                            1. 0xd34df00d
                                                                                                              02.07.2018 17:17

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


                                                                                                1. 0xd34df00d
                                                                                                  02.07.2018 17:15
                                                                                                  +1

                                                                                                  Вопрос в том, могу ли я пообещать ерунду во всём коде вообще или лишь в unsafe-блоках.


                                                                                          1. Dima_Sharihin
                                                                                            02.07.2018 09:59

                                                                                            приводит в плюсы недостатки проектирования

                                                                                            Ну вот у меня есть микроконтроллер, на нем Сишная библиотека, проект на крестах. Поскольку в эмбеддеде все плохо (нет, не так, все ОЧЕНЬ ПЛОХО) с системным окружением, то каждый лепит свои велосипеды. И на каждый выделяемый в системе объект есть свой Си-шный деструктор. Вместо того, чтобы переписывать хитрозапутанный TCP-шный стек на ржавчину, я просто беру и оборачиваю все эти объекты в более безопасный unique_ptr, чтобы ненароком не забыть освободить динамически выделенную память.


                                                                                            1. humbug
                                                                                              02.07.2018 10:15

                                                                                              я просто беру и оборачиваю все эти объекты в более безопасный unique_ptr

                                                                                              Угу. Без unique_ptr прям жить нельзя. Можешь почитать про деструкторы. Говорят, это встроено в С++98.


                                                                                              1. Dima_Sharihin
                                                                                                02.07.2018 10:31

                                                                                                И руками писать move-конструктор? Лень


                                                                                                1. humbug
                                                                                                  02.07.2018 10:33

                                                                                                  В C++98? Дааа.


                                                                                                  1. Dima_Sharihin
                                                                                                    02.07.2018 10:35
                                                                                                    +1

                                                                                                    Писать на цепэпэ98 — это издевательство над личностью.


                                                                                                    1. humbug
                                                                                                      02.07.2018 10:39
                                                                                                      -1

                                                                                                      Пейсать на цепэпэ — это издевательство над личностью.


                                                                                        1. 0xd34df00d
                                                                                          02.07.2018 17:14
                                                                                          +2

                                                                                          Вот в этом и проблема, что время жизни переменной как синтаксической сущности отделено от времени жизни объекта как, ну, как он там в Стандарте определяется.

                                                                                          Это нормально для плюсов, но от этого оно не перестаёт быть проблемой.


                                                                                  1. Dima_Sharihin
                                                                                    02.07.2018 09:27

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

                                                                                    Ну если погромист решил погеройствовать и явно дергает сырые указатели из shared_ptr/unique_ptr — то это его проблемы. Суть как раз в том, чтобы не оставлять сырых указателей где-то еще, помимо самой обертки.


                                                                                    Единственная проблема — легаси код, который про unique_ptr, shared_ptr знать не знал.


                                                                                    unique_ptr/shared_ptr ни за что и не отвечают

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


                                                                                    1. humbug
                                                                                      02.07.2018 09:37
                                                                                      +1

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

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


                                                                                      Потому что RAII хорош только в том случае, когда ты используешь так называемые "правила хорошего тона".

                                                                                      Ага, классная суть. При этом у умных указателей в API вовсю наружу висят сырые указатели, типа всяких unique_ptr::get. Они есть, но ты их не трогай, ага.


                                                                                      1. Dima_Sharihin
                                                                                        02.07.2018 09:46

                                                                                        Они есть, но ты их не трогай, ага.

                                                                                        Именно. Но объявлять устаревшим старое API — это делить на ноль то, ради чего кресты держат обратную совместимость.


                                                                                      1. eao197
                                                                                        02.07.2018 09:47

                                                                                        Они есть, но ты их не трогай, ага.
                                                                                        Они есть поскольку есть гигатонны старого C++ного и еще дофига разного plain-old-C кода, с которым нужно взаимодействовать. А если в современном коде указатель лежит в unique_ptr/shared_ptr, то как иначе вытащить его наружу для передачи в старый/сторонний код?


                                                              1. eao197
                                                                01.07.2018 22:02

                                                                Ну и не могу отказать себе в удовольствии. Раз уж вы хотите указать на косяки «контроля» в C++, то давайте сделаем пример еще проще:

                                                                int main() {
                                                                  int a = 0;
                                                                  { std::unique_ptr<int> b(&a); } // Oops!
                                                                }

                                                                Ну и теперь можно накатать целый трактат на тему отсутствия контроля времени жизни в C++.


                                                                1. humbug
                                                                  02.07.2018 04:08

                                                                  Это не проблема отсутствия контроля времени жизни в С++, это проблема отсутствия unsafe. Если бы в C++ добавили unsafe, то можно было бы написать аналог unsafe метода Box::from_raw. Таким образом ты или пишешь unsafe { Box::from_raw(&10); } (что требует от тебя повышенного внимания к коду и проверки того, что &10 является корректным аргументом (иначе ты ССЗБ)), или у тебя не скомпилируется программа.


                                                                  А целый трактат про unsafe уже написан, встречайте The Rustonimicon.


                                                                  1. eao197
                                                                    02.07.2018 07:13

                                                                    Это не проблема отсутствия контроля времени жизни в С++, это проблема отсутствия unsafe.
                                                                    Извините, но вы переворачиваете все с ног на голову. В C++ все небезопасно, это его идеология. Поэтому в C++ все изначально unsafe и, если чего-то в C++ не хватает, так это введения safe-подмножества, а вовсе не выделение отдельного unsafe.

                                                                    А раз так, то вы C++ вы отвечаете за то, что передаете в unique_ptr — уникальный указатель, не уникальный указатель, указатель на динамически созданный объект или указатель на значение на стеке (что, вообще-то говоря, так же может иметь смысл). Это находится вне зоны ответственности unique_ptr. Как и вне зоны ответственности Box-а в Rust-е.

                                                                    А вот если сравнивать сами unique_ptr и Box, то чем один лучше другого?


                                                              1. dev96
                                                                01.07.2018 22:59

                                                                хорошо, что тут еще и не втянули std::weak_ptr, std::auto_ptr (который уже давно deprecated по понятным всем историческим причинам) и, конечно же, boost::intrusive_ptr (своеобразный shared_ptr).
                                                                Но всё же, во-первых, умные указатели являются не частью языка, а частью библиотеки C++. Появились они в недрах плюсовиков еще до появления Rust со своей пачкой умных указателей, на каждый случай и со стат. анализатором, как примесь к компилятору.
                                                                Но в плюсах то и голому указателю находиться применение. Например, мне не нужно атомарное копирование shared_ptr, мне не нужно перемещение указателя unique_ptr. Окей. На этот случай есть константная ссылка. Но, в тех же аллокаторах может понадобиться какая-нибудь адресная арифметика.
                                                                Плюс, назревает появление какого-то std::observer_ptr в копилку…
                                                                В Rust из коробки есть то, что появилось со временем в плюсах еще до появления Rust, только с блэкджеком. И что?
                                                                Этот весь бесполезный и не логичный спор напоминает мне противостояние винды(Rust) и Linux(C++).
                                                                Rust пока еще молодой язык, который не успел нагрешить и не несёт на себе тяжёлый груз обратной совместимости. И неизвестно еще чьи грехи будут страшнее.


                                                            1. Antervis
                                                              01.07.2018 22:01
                                                              +3

                                                              вам говорят о том, что из-за правил раста и основанных на этих правилах гарантиях Box/Rc/Arc зачастую не нужны там, где они нужны* в с++. И сравнивают они, соответственно, умные указатели из с++ с обычными ссылками из раста.

                                                              *спорный момент. Да, shared_ptr/unique_ptr предоставляют гарантии, которых не дают простые ссылки. Но в однопоточных сценариях используются в основном вторые, потому что проследить за временем жизни объекта несложно.

                                                              Единственный аргумент про лучше/хуже — shared_ptr/unique_ptr дольше печатать чем Box/Rc/Arc. Я могу добавить, что первые поддерживают deleter'ы.


                                                              1. eao197
                                                                01.07.2018 22:05
                                                                -1

                                                                И сравнивают они, соответственно, умные указатели из с++ с обычными ссылками из раста.
                                                                Это я понимаю. Я не понимаю, почему они так делают.
                                                                спорный момент
                                                                Вот именно. Более чем спорный, особенно в современном C++, в котором есть move semantic и возврат/передача «по значению» после C++11 заменяет множество случаев, где в старом C++ использовали, в том числе, и динамическую память.


                                                            1. 0xd34df00d
                                                              02.07.2018 17:10

                                                              Тред большой, я не осилил, но, как я понимаю, принципиальная разница в том, что в расте есть аффинные типы, а в плюсах их нет, и гарантировать средствами системы типов языка вы их никак не можете. shared/unique_ptr вам только гарантируют отсутствие некоторого класса ошибок, и то, если вы мамой клянётесь не сохранять никуда надолго результат *_ptr::get, не делать на нём delete руками, не передавать пустые делитеры (хотя, так как в расте типы аффинные, а не линейные, как я понимаю, там течь тоже можно). А вот отсутствие всяких гонок и прочего никакие поинтеры вам не гарантируют.

                                                              Но я раст не знаю так-то и не пишу на нём.


                                                              1. eao197
                                                                02.07.2018 17:19

                                                                Тред большой, я не осилил
                                                                Квинтэссенция здесь.


                                                                1. 0xd34df00d
                                                                  02.07.2018 17:28
                                                                  +3

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

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


                                                                  1. eao197
                                                                    02.07.2018 17:50

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

                                                                    PS. Извините, на остальные комментарии не могу отвечать. Тред уже исчерпал лимиты терпения и толерантности. Если есть желание о чем-то пообщаться предметно, то можно через личку.


                                                                    1. 0xd34df00d
                                                                      02.07.2018 19:27

                                                                      Ну, от Go уже лично меня воротит. Но вы, наверное, правы, особенно в контексте того абзаца.


                                    1. alexeykuzmin0
                                      01.07.2018 18:43

                                      в C++, как и в Rust-е, вы не можете возвращать ссылку/указатель на член локального вектора. Только в C++ вам за это по рукам никто не бьет
                                      Вообще-то, бьют по рукам, только не на этапе компиляции, а на этапе статического анализа. Правило F43 Core C++ Guidelines.


                                      1. eao197
                                        01.07.2018 18:54

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


                                        1. alexeykuzmin0
                                          01.07.2018 19:00

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


                                          1. eao197
                                            01.07.2018 19:02
                                            +1

                                            Кстати говоря, современные gcc и clang довольно часто выдают предупреждения, когда из функции пытаешься вернуть ссылку/указатель на временный объект. И, есть надежда, что по мере проработке C++ Core Guidelines и анализаторов для них, эти диагностики будут становиться только лучше.


                                          1. PsyHaSTe
                                            01.07.2018 19:12
                                            +1

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

                                            Rust тут выступает по сути как язык с ограничениями, которые позволяют упростить анализ и сделать его более надежным и более умным. По сути раст это и есть же в некоторой степени «С++ со статическим анализатором из коробки», в котором по ходу дела поправили самые болевые места. Я тут недавно пересматривал первую презентацию по расту (2010 год!), так там одной из киллер-фич языка было «Nothing new. Using only proven features».


                                            1. alexeykuzmin0
                                              01.07.2018 22:08

                                              Rust тут выступает по сути как язык с ограничениями, которые позволяют упростить анализ и сделать его более надежным и более умным.
                                              Вот это очень здорово и очень интересно. А какие именно есть проверки, которые статические анализаторы C++ делать не умеют и вряд ли научатся? Я подозреваю, что спрашиваю что-то очевидное из-за недостатка опыта общения с Rust, буду рад, если кинете ссылку.


                                              1. humbug
                                                02.07.2018 03:53

                                                Например, можно ли модифицировать данные в другом потоке? Можно ли разделить переменную в нескольких потоках на чтение?


                                          1. nexmean
                                            01.07.2018 19:47
                                            +3

                                            Ну нет. Статически анализ — это уже инструмент, но тут важен сам язык. А разница в языках тут огромна. Rust безопасен с точки зрения memory safety и data races по дефолту. Чтобы знать Rust вам нужно знать правила владения и заимствования встроенные в язык, чтобы ваш код скомпилировался. Соответственно за человека, который знает Rust можно не волноваться, что он будет писать код, который противоречит здравому смыслу (с точки зрения memory safety и data races). C++ же напротив небезопасен по дефолту, какой-нибудь зелёный джун скомпилирует свой код несмотря на то, что он не корректен, а фидбек по этому поводу он получит в лучше случае во время статического анализа на локалхосте, а в худшем — когда у вас что-то упадёт. При этом чтобы сделать его безопасным нужно выставить какие-то флаги компиляции, да ещё и статический анализатор прикрутить к CI. Соответственно к коду человека, который выучил C++ у вас не будет никакого доверия пока вы не удостоверитесь что он выучил стандарт, C++ Core Guidlines и ещё может быть что-то, не знаю что сейчас должен знать каждый уважающий себя разработчик на C++.


                                            Rust банально проще для новичков, а проще он от того, что его компилятор помогает учиться писать корректный код. В связи с этим можно сделать достаточно простой вывод относительно будущего Rust'а и C++: Rust предоставляет во многом те же профиты что и C++, только вот нанять программировать на Rust'е можно какого-нибудь жсера, которых сейчас пруд пруди, или вообще студента без опыта, достаточно быстро научить его писать код на Rust(при этом большую часть работы по обучению сделают растбук с компилятором) и спокойно принимать от него PR'ы без необходимости проводить долгое и тщательное код ревью. С C++ такой трюк, насколько я знаю, не пройдёт, вы либо нанимаете джунов, а потом долго и мучительно их учите прежде чем они начнут делать больше пользы, чем приносить вреда, либо нанимаете команду матёрых C++ программистов с 5+ лет опыта, которым платите 300kk/nanosec.


                                            1. humbug
                                              01.07.2018 20:27
                                              +1

                                              нанимаете команду матёрых C++ программистов с 5+ лет опыта, которым платите 300kk/nanosec.

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


                                            1. alexeykuzmin0
                                              01.07.2018 22:20

                                              Чтобы знать Rust вам нужно знать правила владения и заимствования встроенные в язык, чтобы ваш код скомпилировался. Соответственно за человека, который знает Rust можно не волноваться, что он будет писать код, который противоречит здравому смыслу (с точки зрения memory safety и data races).
                                              Все же человек обычно программирует не в блокноте. Вам в любом случае потребуются pre-commit hooks для проверки того, что код соответствует style guide и собирается. Добавить туда же проверку, что статический анализ не выдает ошибок, и неожиданно все те же самые рассуждения применимы и к C++.
                                              При этом чтобы сделать его безопасным нужно выставить какие-то флаги компиляции, да ещё и статический анализатор прикрутить к CI.
                                              Так это же хорошо, разве нет? Можно проверять, можно не проверять — это лучше, чем всегда проверять. Ну и ЕМНИП по умолчанию флаги включены, как минимум, в Visual Studio.
                                              к коду человека, который выучил C++ у вас не будет никакого доверия пока вы не удостоверитесь что он выучил стандарт, C++ Core Guidlines и ещё может быть что-то
                                              Core C++ Guidelines отсекает немалую часть дебрей. Выучить только то подмножество C++, которое ограничено Core C++ Guidelines, проще, чем выучить весь стандарт.
                                              Rust банально проще для новичков
                                              Fair point.
                                              либо нанимаете команду матёрых C++ программистов с 5+ лет опыта, которым платите 300kk/nanosec.
                                              Это еще не факт, что минус, для того самого матерого C++ программиста с 5+ лет опыта и высокой зарплатой.


    1. 0xd34df00d
      30.06.2018 17:16
      +1

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

      Правда, на другой стороне спектра люди, пытающиеся притащить в стандарт графику.


  1. DareDen
    30.06.2018 12:27

    А мне понравилось рекурсивное определение make_visitor, явно не тянет на rocket science, как пытается представить автор. Хотя мне и Пролог нравится ;).


    1. Sdima1357
      30.06.2018 13:49

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


      1. PsyHaSTe
        01.07.2018 18:56

        Зависит от задачи. Кстати, тот же раст (надеюсь не утомил отсылками к нему, если что, дайте знать) переписывает подсистему компилятора отвечающую за трейты как раз на prolohish анализатор.


        1. Sdima1357
          01.07.2018 20:36

          Для моих целей Rust слишком сырой.Я пишу программы для диагностического медицинского оборудования уже лет 20. С++ старое и хорошо известное зло. Добавив немного кодогенерации на прологе, лиспе или даже питоне можно обойти большинство узких(неудобных) мест С++. Да и мне не надо писать быстро, мне надо — надежно. «Лучше день потерять, потом за пять минут долететь»-«C» :)


          1. PsyHaSTe
            02.07.2018 00:04
            +1

            Ну как по мне, единственная проблема Rust сейчас — небольшое количество качественных библиотек. Но для медицинского оборудования я как раз бы предпочел раст (при условии, что есть необходимые библиотеки. Если нет, то деваться некуда). Язык молодой, это да, но смотря с чем сравнивать, как говорится.


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


            1. Sdima1357
              02.07.2018 00:44

              Мы ведь обсуждаем нововведения в с++? Нравится Вам раст, пишите на нем на здоровье, а я немного подожду пока устаканится. Ну как Вам объяснить? Ну вот к примеру научные тексты на русском формулируются весьма коряво. Но ценность самой научной работы от языка зависит мало и для смены языка нужно более веские основания чем мода. А раст пока сырой. Вот напишут на нем пару ядер для операционок, соберут компиляторы под микроконтроллеры и GPU :) — вот тогда и поглядим. Пролог тоже в свое время (лет 30 назад) декларировался как убийца процедурных языков (японцы угроХали кучу денег на параллельный пролог) и где он теперь?


              1. DarkEld3r
                02.07.2018 02:21

                А раст пока сырой.

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


                Ну и с моей колокольни это смотрится забавно рядом с кодогенерацией (С++) на прологе и лиспе.


              1. humbug
                02.07.2018 03:55

                пару ядер для операционок

                есть


                соберут компиляторы под микроконтроллеры

                есть


                и GPU :)

                есть


                — вот тогда и поглядим

                Глядите)) Ждем отчетов.


                1. Sdima1357
                  02.07.2018 10:39
                  -4

                  На большинстве тестов раст проигрывает, причем значительно, иногда почти вдвое
                  benchmarksgame-team.pages.debian.net/benchmarksgame/faster/rust-gpp.html
                  Так что в вашу секту я пока не записываюсь. Развлекайтесь без меня.


                  1. eao197
                    02.07.2018 10:46
                    +4

                    Эти тесты уже давно не принято упоминать в приличном обществе :)


                    1. Sdima1357
                      02.07.2018 11:34
                      -3

                      Эксперименты вообще вещь неприличная. Мешают таким красивым теоритичиским построениям


                      1. eao197
                        02.07.2018 11:43
                        +2

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


                  1. humbug
                    02.07.2018 10:48
                    +1

                    Если вам не важна безопасность, то конечно же не вступайте. Бегите прочь! Иначе ваши программы будут работать стабильно, вы будете проводить раз в 500 меньше времени в отладчике. И зарплата будет раза в 3-4 выше. Бегите! Не создавайте конкуренцию :D


                    1. Sdima1357
                      02.07.2018 11:29
                      -1

                      Если у Вас отладка стала занимать в 500 раз меньше времени, значит на с++ Вы писать не умели и могу Вам только посочувствовать. Я и на с++ отладчиком пользуюсь только для чужого кода. Дам подсказку. Правильный дизайн сокращает время отладки до нескольких процентов от времени написания кода. И неважно на каком языке.


                      1. humbug
                        02.07.2018 11:36

                        значит на с++ Вы писать не умели и могу Вам только посочувствовать. Я и на с++ отладчиком пользуюсь только для чужого кода.

                        А кто сказал, что ошибки были в моем коде? Откуда столько грубости?


                        Software architect в вашем лице не умеет играть в командную работу?


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

                        Дам подсказку. Если не писать программу, то в ней вообще не будет ошибок и она будет исполняться мгновенно.


                      1. humbug
                        02.07.2018 11:40

                        Правильный дизайн сокращает время отладки до нескольких процентов от времени написания кода

                        Или может Software architect в вашем лице боится потерять работу, которую может выполнить компилятор?


                      1. humbug
                        02.07.2018 11:46
                        +1

                        И вообще, человек с должностью Software architect должен слышать про цикломатическую сложность, а не городить for/if/for/if/for/if.


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


                        1. Sdima1357
                          02.07.2018 15:22
                          -2

                          вы будете проводить раз в 500 меньше

                          Ну вообще-то это Вы сказали, а я вам поверил и предположил, что из Вашего собственного опыта. Или нет?
                          цикломатическую сложность, а не городить for/if/for/if/for/if.

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

                          Аргументы «сперва добейся» стоит раздавать

                          Где? Вы меня с кем то спутали…


                          1. humbug
                            02.07.2018 18:31

                            Больше доверяйте компилятору, повнимательней гляньте на ассемблер, во что это компилируется :)

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


                            1. Sdima1357
                              02.07.2018 19:06
                              -2

                              Вам известен способ обхода соседей в многомерном массиве короче чем вложенный for / if / for if /for в нескольких строчках кода? Поделитесь :). Для меня код достаточно прозрачный, ключевое слово — «достаточно».
                              Впрочем я не спорю на религиозные темы с адептами религии и ревнителями чистоты кода. Поэтому заканчиваю дискуссию. Удачи Вам с RUST-ом…


          1. 0xd34df00d
            02.07.2018 17:24
            +1

            Coq возьмите. Тоже старое и хорошо известное зло.


  1. thatsme
    30.06.2018 14:31
    +2

    Первый пример с визитором и printf это конечно отдельный перл (в статье про c++17)…
    Что помешало сделать так?


    std::visit([](auto&& arg){std::cout << arg;}, variant_instance);

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


    P.S.: Статья о том как не осилил constexpr и std::visit.


  1. valeriyvan
    30.06.2018 14:34
    +2

    Боже, храни Swift с его enum с ассоциированными значениями. Современный C++ превращается в какой-то лютый пиздец, не побоюсь этого слова.


    1. thatsme
      30.06.2018 16:34

      Да ладно, так уж и писец. У всего своя область применения. А если автор статьи не осилил в совокупности, то что ему упростит работу, а выдрал кусочек и привязал его к С++11 или тем более к С, то это не вина языка, а вина неосилятора. Конечно на интерпретируемых языках или языках с динамической компиляцией ака JIT всё это делается гораздо проще, но они за это тоже платят (например сборкой мусора и джиттером во время сборки).


      1. nexmean
        30.06.2018 16:55

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


        1. thatsme
          30.06.2018 17:29
          +1

          Вобщем неважно в чём причина, в грузе-ли лет, или в недостатках архитекутры. Речь то была о том что «писец» или не «писец». Я как пользователь С++, утверждаю, что никакой ни писец, а вся проблема в том, что автор не осилил нововведения и за уши их притягивает в старый стандарт.


          1. nexmean
            30.06.2018 17:41

            Вы как пользователь C++ предвзяты и не можете объективно оценивать реальность, точно так же как пользователь Swift выше, или как я, пользователь Rust. Для меня и valeriyvan это пипец, для вас нет, это всего лишь субъективные мнения, не стоит из-за этого начинать вешать ярлыки неосиляторов направо и налево.


            Однако отсутствие типов сумм как объектов первого класса в C++ это объективный факт, с которым спорить нельзя.


            1. eao197
              30.06.2018 17:51

              Вы как пользователь C++ предвзяты и не можете объективно оценивать реальность, точно так же как пользователь Swift выше, или как я, пользователь Rust.
              Странное заявление. Пользователь С++ оценивает положение дел в C++. Какое отношение к этому имеют пользователи Swift-а или Rust-а?
              Однако отсутствие типов сумм как объектов первого класса в C++ это объективный факт, с которым спорить нельзя.
              Ну да, их в C++ нет. И что делать C++никам? Менять свой инструмент на какой-то другой или учиться таки пользоваться тем, что в языке есть?


              1. nexmean
                30.06.2018 18:06

                Странное заявление. Пользователь С++ оценивает положение дел в C++. Какое отношение к этому имеют пользователи Swift-а или Rust-а?

                Странный вопрос, вы регулярно даёте личную оценку Rust'у в личном блоге, да и не только. С чего бы вдруг мы не могли давать личную оценку C++?


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

                С++ники могут делать что хотят, автор, например, отрицательно отзывается о языке, кто-то старается в своей работе задействовать тот же Rust/Swift/<LANGNAME>, а кому-то удобнее продолжать использовать C++ и не искать в нём недостатки. Что делать вам — ваше личное дело.


                1. eao197
                  30.06.2018 18:13

                  Странный вопрос, вы регулярно даёте личную оценку Rust'у в личном блоге, да и не только.
                  Вопрос не странный, вы просто не поняли его.
                  С чего бы вдруг мы не могли давать личную оценку C++?
                  Да это сколько угодно. Но когда C++ник оценивает возможности и особенности C++, то странно обвинять его в предвзятости. Особенно на фоне личного мнения пользователей других языков программирования.
                  Что делать вам — ваше личное дело.
                  Если комментарий вида «Однако отсутствие типов сумм как объектов первого класса в C++ это объективный факт, с которым спорить нельзя.» нужен только для того, чтобы высказать свое личное мнение, то это одно. Но это ничем не отличается от надписи на заборе «Вася — дурак».

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


                  1. nexmean
                    30.06.2018 18:33

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

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


                    Если комментарий вида «Однако отсутствие типов сумм как объектов первого класса в C++ это объективный факт, с которым спорить нельзя.» нужен только для того, чтобы высказать свое личное мнение, то это одно. Но это ничем не отличается от надписи на заборе «Вася — дурак».

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

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


                    1. eao197
                      30.06.2018 18:42

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

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

                      А вот персонажи, которые приходят сказать, что в Swift что-то лучше, чем в C++. Или что в Rust-е что-то лучше. Или что еще где-то что-то лучше. Или что в C++ чего-то нет… Вот эти персонажи напоминают тех, кто хочет просто написать на заборе свое веское мнение. И которым, по большому счету, пофиг на тех, чей это забор.


                      1. DarkEld3r
                        02.07.2018 02:25

                        И которым, по большому счету, пофиг на тех, чей это забор.

                        Дык, именно этот забор — "общественный". (:


                    1. 0xd34df00d
                      30.06.2018 19:22

                      Да не, просто придираться к визиторам и тому подобному — это, ну, такое.

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


              1. PsyHaSTe
                01.07.2018 11:32

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


                Мое мнение, что стоит менять инструмент. Выгодно если думать на перспективу. Напомню цитату из «побеждая посредственность»:

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


                Изучение инструмента, которое повышает продуктивность — всегда полезно.


                1. eao197
                  01.07.2018 11:43

                  Мое мнение, что стоит менять инструмент.
                  Предсказуемо. Допустим, сменить инструмент слишком дорого. Что тогда?
                  Изучение инструмента, которое повышает продуктивность — всегда полезно.
                  А изрекать банальности — это как?

                  Давайте оставим за скобками тезис о том, повышает ли продуктивность Rust, и посмотрим вот с какой стороны: вы отвечаете за большую кодовую базу на C++. Изучили Rust и осознали, что Rust повышает продуктивность. И? Переведете кодовую базу в режим сопровождения и начнете писать новый код на Rust?


                  1. PsyHaSTe
                    01.07.2018 12:54

                    Давайте оставим за скобками тезис о том, повышает ли продуктивность Rust, и посмотрим вот с какой стороны: вы отвечаете за большую кодовую базу на C++. Изучили Rust и осознали, что Rust повышает продуктивность. И? Переведете кодовую базу в режим сопровождения и начнете писать новый код на Rust?

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


                    1. eao197
                      01.07.2018 12:59

                      Тогда все просто: если вас не устраивает C++, вы меняете работу и проблемы C++ вас больше не волнуют. Ну а тем, кто по тем или иным причинам остается с кодовыми базами на C++, приходится использовать то, что есть. И мнение не-C++ников по поводу С++ просто игнорируется, как не приносящее практической пользы.


                  1. freecoder_xx
                    01.07.2018 14:12

                    И? Переведете кодовую базу в режим сопровождения и начнете писать новый код на Rust?

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


                    1. eao197
                      01.07.2018 14:18
                      +1

                      Собственно, почему бы и нет? )
                      Потому что шансы сделать это и выиграть обратно пропорциональны размеру кодовой базы. Если вы отвечаете за проект в 50KLOC, который еще и неактивно развивается, то почему бы и нет?

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

                      Попытки совместить разработку и на C++ и на чем-то новом будут требовать массу усилий, расширения команд, увеличение бюджетов и т.д., и т.п. Хорошо, если у вас проект разбит на модули, которые взаимодействуют друг с другом через IPC механизмы или через БД. Тогда можно новые модули писать на Rust, старые развивать на C++. Но это вряд ли характерно для ниш, в которых C++ еще живет, имхо.


                      1. PsyHaSTe
                        01.07.2018 14:23

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

                        То есть мозилла потратила тонну времени и денег чтобы угробить собственный проект (который несколько больше 50 KLOC)?


                        1. eao197
                          01.07.2018 14:32

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

                          ЗЫ. На вопросы-то найдите силы ответить, не сливайтесь. А то вы оставляете впечатление человека, который не понимает, о чем говорит.


                          1. PsyHaSTe
                            01.07.2018 14:41

                            История с Mozilla, Firefox и попытками заменить кодовую базу Firefox-а с C++ на Rust как раз наглядно показывает, насколько все это трудоемко и долго.

                            Да, и несмотря на это они этим занимаются. Наверное, им никто не сказал, что это бесполезная трата времени!

                            ЗЫ. На вопросы-то найдите силы ответить, не сливайтесь. А то вы оставляете впечатление человека, который не понимает, о чем говорит.

                            Я не собираюсь отвечать на вопросы «бот ли я».


                            1. eao197
                              01.07.2018 14:49

                              Да, и несмотря на это они этим занимаются. Наверное, им никто не сказал, что это бесполезная трата времени!
                              Вы, кажется, не умеете читать. А именно, вы не увидели следующее:

                              «Если же размеры кодовой базы превышает несколько сотен KLOC и проект постоянно дорабатывается и обрастает новыми фичами, то кардинальная смена языка реализации — это большие шансы угробить проект.» — здесь нет речи о бесполезности и невозможности. Речь идет о рисках. Mozilla может себе позволить такие риски. Это вовсе не значит, что такая ситуация у всех.

                              «Попытки совместить разработку и на C++ и на чем-то новом будут требовать массу усилий, расширения команд, увеличение бюджетов и т.д., и т.п.» — а вот это как раз и можно наблюдать.

                              Я не собираюсь отвечать на вопросы «бот ли я».
                              Тогда хотя бы расшифруйте свой пассаж про семантику unique_ptr/shared_ptr.


                              1. nexmean
                                01.07.2018 15:25

                                «Попытки совместить разработку и на C++ и на чем-то новом будут требовать массу усилий, расширения команд, увеличение бюджетов и т.д., и т.п.» — а вот это как раз и можно наблюдать.

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


                                1. eao197
                                  01.07.2018 15:28

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


                  1. 0xd34df00d
                    02.07.2018 17:31
                    +2

                    Хрен с ней с кодовой базой. Что с людьми делают, которые на плюсах могут писать хоть что-то и как-то, а на расте не могут совсем? Что делать с их начальниками?


                    1. nexmean
                      02.07.2018 18:35
                      +2

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


                      1. 0xd34df00d
                        02.07.2018 19:29

                        Ну, некоторые плюсовики освоили что-то вроде C++98 лет 15-20 назад и с тех пор так и пишут, осваивая что-то новое ну уж когда совсем припрёт.


                        1. PsyHaSTe
                          03.07.2018 00:05

                          Ну значит они будут постепенно вытеснены с рынка программистами, которые будут более продуктивны с более удобными инструментами. Как по мне, основная работа программиста в том, чтобы постоянно осваивать новые инструменты. Не трюки, нет, их потом разбирать никто не захочет, а именно инструменты, которые повышают скорость и качество разработки без ущерба. И если человек просто ходит на одну и ту же работе 15-20 лет, и учит что-то только «когда совсем припрёт», то видимо он изначально шел не потому, что ему это нравилось, а потому где-то услышал, что на этом денег можно сколотить.

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

                          Я сам шарпист, и я изучаю раст, потому что он мне кажется проще и понятнее шарпов. Да, синтаксис изначально пугает, да, борручекер вначале не дает натворить глупостей и ругается, но все это изучается, причем изучается быстро. Мне хватило буквально пары месяцев относительно интенсивного написания кода, чтобы начать контрибутить в такие сложные проекты, как например Parity (и я не про исправления в readme, а например перенос кода на последний компилятор). И это мне, человеку, который пришел из языка с GC! Плюсовик вообще наверное за выходные может спокойно освоить.

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


            1. Overlordff
              30.06.2018 17:54

              Но ведь в С++ std::variant (представляющий тип сумму точно также, как std::function представляет функцию) — это как раз объект первого класса…


              1. nexmean
                30.06.2018 18:13

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


                1. 0xd34df00d
                  30.06.2018 19:20

                  А это самоцель?


                  1. thatsme
                    30.06.2018 21:54
                    +3

                    Судя по всему, — да. Раширяемость языка с помощью тьюринг полного языка шаблонов на этапе компиляции «растистов» не устраивает и префикс std:: их коробит (я бы даже сказал «ржавит»). Мне вот всё равно в DSL это реализуется или в самом языке, — результат один.


                    1. 0xd34df00d
                      01.07.2018 00:30

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

                      Быстрее бы уже всякие reflexpr завезли.


                    1. eao197
                      01.07.2018 10:00

                      Если бы алгебраические типы данных и паттерн-матчинг были поддержаны на уровне языка, а не стандартной библиотеки, то мы могли бы иметь в языке более продвинутый switch, в котором можно было бы делать и декомпозицию значений (по аналогии с тем, что делает structured binding в C++17), и использовать гарды (условия в отдельных case), и имели бы контроль со стороны компилятора за полнотой вариантов в switch (как это сейчас делается для switch-ей по enum class-ам). Так что с точки зрения теории, поддержка АлгТД+паттерн-матчинга на уровне компилятора лучше оной в stdlib.

                      Поэтому если чей-то интерес состоит в том, что бы в какой-то абстрактной табличке сравнения фич языков программирования поставить в строке «паттерн-матчинг» прочерк для C++ и галочку для Rust-а, то тогда претензии к C++ понятны.

                      Но если смотреть на происходящее с точки зрения пользователей C++, то ситуация становится совсем другой. До C++17 std::variant и std::visit в С++ вообще не было. Если кто-то хотел подобной функциональности, то ему нужно было либо делать свои велосипеды, либо подтаскивать в свой проект сторонние зависимости. А сейчас это все доступно из коробки. Так что с точки зрения действующего C++ программиста, std::variant/std::visit — это сильно много лучше, чем ничего. И претензии по поводу того, что sum-types в C++ добавились не через расширение компилятора, а через расширение stdlib выглядят достаточно абсурдно.


  1. kloppspb
    30.06.2018 17:55

    template typename std::enable_if<!std::is_reference::value, int>::type

    Я не то что пугаюсь, просто недоумеваю.


  1. Greendq
    30.06.2018 18:13

    У меня такое впечатление, что все языки программирования активно занимаются заимствованием. Теперь С++ нельзя назвать языком со строгой типизацией. Эдак мы до обычных человеческих языков скатимся с их неоднозначностью…


    1. nexmean
      30.06.2018 18:19

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


    1. Overlordff
      30.06.2018 18:44
      +1

      Не хочется Вас расстраивать, но С++ никогда не был языком со строгой типизацией :). Со статической да, но не строгой.


  1. yeputons
    30.06.2018 18:32
    +2

    Мы начали с простой цели: посмотреть на содержимое сигма-типа.

    Есть же способы проще visit, особенно если мы хотим проверить один конкретный случай:


    • std::size_t std::variant::index(), который вернёт номер варианта.
    • bool std::holds_alternative<T>(), который проверит, верно ли, что внутри лежит T.
    • std::get<T>() и std::get<std::size_t>(), которые позволяют получить значение по типу или номеру.


    1. eao197
      30.06.2018 18:45
      +2

      Плюс еще и std::get_if.


  1. 0xd34df00d
    30.06.2018 19:24

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

    Понимаю, что перевод, но всё же.


  1. Antervis
    30.06.2018 20:24

    в отличие от типов-сумм в других языках (и std::any), std::variant не является в полной мере type-erased. Это нужно для универсальности, zero overhead и вычислений в constexpr контексте. Подразумевается, что под конкретную задачу его можно сравнительно легко обернуть.

    Ну а про существование более простых методов «обхода» std::variant уже писали.


    1. 0xd34df00d
      01.07.2018 00:31

      Эм, а зачем типу-сумме быть type-erased?


  1. naething
    30.06.2018 21:59
    +9

    На работе пришлось последний месяц-два писать на Rust, и после этого C++ кажется жутко неудобным и неотёсанным поделием. Отсутствие типов-сумм внезапно стало очень сильно напрягать, а семантика перемещения в C++ кажется какой-то жалкой пародией и костылем — нет никакой помощи от компилятора для отслеживания использования перемещенных объектов. Шаблоны с утиной типизацией и отвратительными сообщениями об ошибках кажутся каким-то безумием по сравнению с системой типажей (trait).

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


    1. eao197
      01.07.2018 10:01
      +1

      Можно поинтересоваться, какой именно C++ вы могли использовать на работе? С++17? С++14? C++11? Что-то более древнее?


      1. naething
        02.07.2018 01:48

        C++14. Активно обсуждается переход на C++17, но есть определенные политические препятствия.


        1. eao197
          02.07.2018 06:57

          Рискну предположить, что вы сравниваете C++14 с современным Rust-ом, а не Rust-ом образца 2014-го года.


          1. naething
            02.07.2018 11:08
            +5

            Это так, но ради справедливости прошу заметить, что упомянутые вещи практически не поменялись в С++17.


            1. eao197
              02.07.2018 11:12

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

              Во-вторых, в C++17 язык поменялся не принципиально, но значительно. [[nodiscard]], if constexpr, structured binding и еще несколько вещей сделали программирование на C++17 гораздо более простым и удобным занятием. Да и касательно темы статьи, трюк с visit и overloaded стал возможен именно благодаря изменениям в C++17.


    1. reversecode
      01.07.2018 12:52
      -1

      Раньше я писал на языке А, пару месяцев как пришлось перейти на язык Б, язык Б лишен недостатков языка А, язык Б работает с первого раза и на нём проще писать…
      :))

      Так могут позволить говорить только те люди которые сделали себе имя на много лет. И код которых можно увидеть как на языке А так и на языке Б.

      Сколько вы до этого писали на языке С++ и какой версии? Есть у вас опыт комерц использования его хотя бы лет 10? Где можно посмотреть ваши проекты на С++ и на Rust?


      1. naething
        02.07.2018 10:48
        +4

        Я не говорю, что Rust обязательно лучше C++ и на нем проще писать во всех контекстах. Просто поделился первыми личными впечатлениями об эргономике языка. Заметьте, что я в своем комментарии обильно использовал слово «кажется», чтобы подчеркнуть, что я делюсь собственным восприятием и не претендую на объективность.

        Считаю ненужным что-то вам доказывать. Про опыт и «покажи проекты» — очень толсто, прямо классический пример «сперва добейся». Если вы считаете мое мнение не заслуживающим внимания, вы можете его просто молча игнорировать.


        1. reversecode
          02.07.2018 11:00

          В вашем случае это получился вброс, о чем я вам тонко намекнул


          1. humbug
            02.07.2018 11:14

            Сколько вы до этого писали на языке С++ и какой версии? Есть у вас опыт комерц использования его хотя бы лет 10?

            Миллионы мух не могут ошибаться!


            1. eao197
              02.07.2018 11:23
              +1

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

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


              1. humbug
                02.07.2018 11:24

                Это да.


    1. dev96
      01.07.2018 16:00

      Поздравляю вас)
      Но хочу сказать, что всё-таки у каждого языка своя ниша и не стоит противопоставлять C++ и Rust.

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

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


      1. nexmean
        01.07.2018 16:25
        +2

        С++ и не задумывался как язык, который позволяет писать элементарный многопоточный код. Напоминаю, что девиз языка «Не плати за то, что не используется», поэтому С++ напоминает конструктор из бесконечного числа мельчайших деталей. Тем-более, в языке изначально не было заложено таких концепций, как «асинхронность» и «потоки», и были притянуты за уши в std лишь в C++11.

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


        Со своими задачами C++ прекрасно справляется. Его стоит использовать там, где требования согласуются с его философией.

        А можно поконкретнее про задачи и философию C++?


        И ничего удивительного нет в том, что в Rust X-фича сделана грамотнее, чем в плюсах.

        И правда ничего удивительного, ведь Rust появился значительно позже. У его разработчиков была возможность посмотреть на ошибки прошлого и, используя те знания и опыт которые человечество накопило в области дизайна ЯП и компиляторостроения, как-то исправить их. У комитета такой возможности нет, ибо на C++ лежит огромный груз обратной совместимости.


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

        Как раз таки область применения у Rust (в перспективе) и C/C++ примерно одна и та же, разве что, ИМХО, у Rust она значительно шире.


        1. Antervis
          01.07.2018 16:52

          И за счет чего у раста область применения станет шире, чем «любая платформа»?

          А можно поконкретнее про задачи и философию C++?

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


          1. PsyHaSTe
            01.07.2018 17:47

            И за счет чего у раста область применения станет шире, чем «любая платформа»?

            Ну, веб-сайты на плюсах сейчас вроде не верстают. А вот шаблонизаторы на C#/Rust есть.

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

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


            1. Antervis
              01.07.2018 18:46

              Ну, веб-сайты на плюсах сейчас вроде не верстают.

              редко, но верстают. QtWebGL, например, делают специально для этого.
              В rust аллокаторы точно так же отключаются, например вот аллокатор который используется для компиляции в wasm.

              не глобальными кастомными аллокаторами

              Имелось в виду переопределение аллокатора только в рамках одного класса


              1. PsyHaSTe
                01.07.2018 18:55

                Имелось в виду переопределение аллокатора только в рамках одного класса

                Прошу прощения, неправильно прочитал. По этим вещам действительно слышал о некоторых подвижках (вроде той же арены), но наверное действительно пока особо похвастаться нечем в этой области.


            1. Dima_Sharihin
              02.07.2018 07:43

              Ну, веб-сайты на плюсах сейчас вроде не верстают.

              Не то, чтобы они были распространены, но все же есть.
              Как пример — https://cutelyst.org/


            1. eao197
              02.07.2018 08:06

              Ну, веб-сайты на плюсах сейчас вроде не верстают.
              Это вы от недостатка информации. В специфических нишах верстают и на C++ и даже на чистом C. Например, Web-морды для каких-нибудь умных устройств (об этом, в частности, рассказывали разработчики Wt).


              1. PsyHaSTe
                02.07.2018 12:16

                Вот прям в промышленных масштабах, а не в качестве эксперимента/нишевого проекта? Потому что я вижу много компаний, где даже новый проект спокойно могут на ASP.Net Core начать делать, но с трудом представляю себе реальный кейз "а давайте на плюсах запилим!". Да, сейчас больше есть тенденций на разделение фронт — реакт/ангуляр/вуй и сервер — что угодно, но и первый сценарий использования всё-в-одном достаточно популярен.


                1. eao197
                  02.07.2018 12:19

                  не в качестве эксперимента/нишевого проекта?
                  Так я же сказал: в специфических нишах. О том, что Web-морды для умных устройств делают на C++ рассказывали разработчики Wt и, если правильно помню, разработчики POCO. Так что это не единичные случаи. Но, по сравнению с массовым формошлепством для Web-а, конечно, это капля.


                1. Antervis
                  02.07.2018 14:26

                  веб-игры часто пишут на плюсах.


          1. nexmean
            01.07.2018 17:52

            не глобальные кастомные аллокаторы, constexpr

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


    1. DarkEld3r
      02.07.2018 02:30

      На работе пришлось последний месяц-два писать на Rust, и после этого C++ кажется жутко неудобным и неотёсанным поделием

      При дальнейшем использовании впечатления могут меняться. (:


      Если что, пишу на расте (фултайм, за деньги) чуть больше года.


      1. naething
        02.07.2018 10:52
        +1

        Охотно верю. Самому интересно, как изменится мое восприятие через год.