После появления в стандартной библиотеке С++ умных указателей, проблема управления временем жизни объекта была решена. Можно создавать объекты на стеке, тогда они автоматичести удалятся при выходе из области видимости, либо использовать unique_ptr для создания объектов с экслюзивным владением или shared_ptr для совместного владения. Но только для shared_ptr в стандартной библиотеке существует невладеющий указатель weak_ptr, который предотвращает использование невалидного указателя. Для остальных случаев используют «старые и опасные» raw pointers.

Как же предлагают решить эту проблему разработчики языка?

На сайте CppCoreGuidelines есть несколько любопытных документов (здесь и здесь).
Основной посыл таков: мне не можем реализовать безопасные невладеющие указатели, не нарушая zero overhead principle. Поэтому мы не будем реализовывать такие средства в runtime, а постараемся решить проблему статическим анализом кода.

Я не согласен с такой позицией и вот мои доводы:

  1. Статический анализ не сможет отловить все проблемы. Сами авторы статей говорят о том, что иногда прийдется помечать некоторые конструкции как безопасные, оставляя вопрос корректности кода на совести разработчика.
  2. Для того, что бы статический анализ заработал, нужна поддержка со стороны компиляторов. Когда она появится — неизвестно.
  3. Статический анализ не позволяет иметь слабые невладеющие указатели (аналог weak_ptr, который зануляется при удалении объекта).
  4. Реализация таких указателей нарушает zero overhead principle — это правда. Но, на мой взгляд, это принцип также нарушают shared_ptr, т.к. имеет дополнительный ref_count объект или, например string с его small string optimization. В любом случае, стандартная библиотека предоставляет классы с четко описанными характеристиками, а программист решает подходят эти классы для него или нет.

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

В своем проекте RSL я попробовал реализовать такой указатель. Основная идея не нова: необходим объект, который при разрушении будет нотифицировать указатели о факте удаления.

Таким образом мы имеем два класса:

  1. rsl::track::trackable — класс, который оповещает указатели при удалении.
  2. rsl::track::pointer — собственно невладеющий указатель.

Когда rsl::track::pointer'ы указывают на один и тот же rsl::track::trackable объект, они выстраиваются в двухсвязный список. Указатель на голову списка содержится в rsl::track::trackable. Таким образом, создание указателей занимает константное время. Размер rsl::track::trackable составляет один указатель, а rsl::track::pointer — 4 указателя (указатель на объект, два указателя для организации списка и еще один для реализации полиморфного поведения). Возможно более оптимальная организация указателей, если кто знает, прошу рассказать.

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

Кроме того, с появлением allocator aware containers, появилась возможность реализовать аллокатор, который позволяет использовать rsl::track::pointer со стандартными контейнерами. Основная идея в том, что теперь все аллокации в контейнерах делаются экземпляром аллокатора, хранящегося в контейнере, или его шаблонной копии, и мы можем хранить rsl::track::trackable в аллокаторе и передавать его в копии.

В тестах приведены примеры работы с основными стандартными контейнерами, включая std::array, а также unique_ptr.

В заключение хочу привести еще один сценарий, в котором rsl::track::pointer будут полезны. Это ситуации, аналогичные delete this. Обычно такое происходит косвенно при вызове внешнего по отношению к объекту кода, функтора или сигнала. Такого рода ошибки редки, но трудно уловимы.
Для таких случаев (да и любых других проблем с доступом по висячей ссылке) используют такие средства как google sanitizers, которые позволяют отлавливать подобные проблемы.

Но эти средства имеют свои недостатки:

  1. Работают не везде (не на всех платформах).
  2. Необходима инструментализация кода — код отличается от продакшена.
  3. Нет аналога weak_ptr, указателя котроый зануляется при удалении объекта.
  4. Детектирует время и место доступа по невалидному указателю. Хотя, как правило, более интересен момент, когда происходит удаление объекта, на который есть указатели. По этой же причине инструментация детектирует не все случаи неправильного доступа, а лишь те, которые реально отработали при тестировании.

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

P.S. Еще раз, для удобства, привожу ссылку на код библиотеки RSL.
Поделиться с друзьями
-->

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


  1. crea7or
    03.04.2017 14:27
    +5

    Элджера читали? 20 лет изобретают сборку мусора для C++.


    1. lexxmark
      03.04.2017 15:23
      +1

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

      По поводу сборки мусора, вы же конечно понимаете, что не удалять объект пока на него есть хотя бы одна ссылка и занулять указатели (или еще как-то реагировать) при удалении объекта — это две большие разницы: о)


      1. crea7or
        03.04.2017 17:58
        +2

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


        1. lexxmark
          03.04.2017 21:12

          Мне кажется С — это язык где всем управляет программист.
          А вот по поводу плюшек верно. И одной из таких плюшек явно не хватает.


          1. crea7or
            03.04.2017 21:56
            +2

            Тут пытаются threadpool протащить в стандартную библиотеку. При желании-то можно всё что угодно навернуть, вот только нужно оно? Для этого есть boost, там подобных хитрых штук много.


            1. lexxmark
              04.04.2017 08:35

              Тут соглашусь…
              после стольких лет топтания на месте язык С++ переживает ренесанс и у многих зачесались ручки добавить в язык кучу «классных» штучек. Меня особо смущает proposal на 2D drawing на основе С библиотеке Cairo.

              Но проблема висячих ссылок универсальная, недаром над ней сейчас активно работают и Бьярне и Герб (см. ссылки в статье). Только вот не давать run-time иструмент для решения, а пытаться частично решить с помощью статического анализа, мне кажется в корне не верна. О чем собственно и статья.


            1. dendron
              04.04.2017 08:38
              +1

              Так уже протащили же. std:async.


              1. lexxmark
                04.04.2017 09:31

                std:async решает проблему с висячими ссылками?


              1. crea7or
                04.04.2017 14:46

                Это не совсем то, в эсинке же нет контроля над потоками.
                Гора Нишанова недавно слушал у Яндекса, там с тредпулом около 25 типов новых уже придумали. То есть мрак вообще. Да и эсинк это не для тех, кто думает о производительности. Скорее плюшка для тех, кто не хочет заморачиваться.


  1. Satus
    03.04.2017 15:01

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


    1. lexxmark
      03.04.2017 15:18
      +1

      Если вы программируете на С++, то понятие dangling pointer вам должно быть знакомо. Коротко — в С++ нет средств для отлова ситуаций, когда у нас есть указатель на удаленный объект.


      1. LooMaclin
        03.04.2017 15:28
        +1

        Попробуйте Rust.


        1. lexxmark
          03.04.2017 21:13
          +1

          Да это понятно: о) Но куда уж мы от С++?: о)


          1. TargetSan
            03.04.2017 22:15
            -1

            Golang, Swift своим вполне себе успехом показывают, что много куда — было бы желание. И если Swift насаждается Apple как монополистом платформы, то Гофер хорошо выстрелил без такого "допинга". Любой успех начинается с малого. А если думать в терминах "куда же мы от Х", то так и будем ещё лет 10-20 с перераздутым стандартом, убогими iostreams и зета-функцией Римана.


            P.S: Эх, где был нынешний bindgen и tokio два года назад...


            1. lexxmark
              04.04.2017 08:41
              +1

              Эволюция наше всё!
              Посоветуйте Линусу слезть с С и переползать на Golang или Rust. Думаю узнаете много идиоматических финских слов: о) (шутка).

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

              P.S: Эх, где был нынешний bindgen и tokio два года назад...

              Вот здесь не уловил.


              1. TargetSan
                04.04.2017 08:48
                +1

                Что я точно не предлагаю — переписывать объёмные проекты. Куча муторной и часто ненужной работы. Вот писать новые модули ядра на Rust вроде как пытаются.


                bindgen — пакет Rust для генерации биндингов к С библиотекам по заголовочным файлам.
                tokio — асинхронный стек на основе фьючерсов для того же Rust.
                Жалуюсь потому, что 2 года назад первый был в зачаточном состоянии, даже константы из макро-дефайнов транслировать не умел, а второго не было в принципе. В противном случае на один новый проект, написанный на Rust вместо С++, было бы больше.


                1. kmu1990
                  04.04.2017 09:24
                  -1

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

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


      1. Satus
        03.04.2017 15:41
        +1

        Ну я знаю, что такое dangling pointer. Но разве shared_ptr и weak_ptr — не средства как раз для того, чтобы у вас не было таких указателей?


        1. TargetSan
          03.04.2017 15:52

          Нет. Они предназначены для общего владения одним объектом из нескольких мест. Но проблему "утёкшего" временного указателя не решают.


          1. Satus
            03.04.2017 15:54
            +2

            Почему нет? Если все shared_ptr были удалены, вы не сможете залочить weak_ptr.


            1. TargetSan
              03.04.2017 16:03

              Сходу могу вспомнить 2 фактора


              1. Оверхед на подсчёт ссылок
              2. Не работает с объектами на стеке


              1. Satus
                03.04.2017 16:29
                +2

                1. Так ведь и предложенное решение не compile-time. Я бы даже готов поспорить, что оно медленнее.
                2. Зачем брать указатели на объекты на стеке? Чтобы обратиться по указателю к объекту на стеке, который уже удалён, надо ещё постараться.


                1. TargetSan
                  03.04.2017 16:49

                  1. Я кстати не говорил, что предложенное решение хорошее. И таки оверхед у него будет поболе чем у shared_ptr.
                  2. А как вы предлагаете передавать объекты не "с потрохами"? Ссылки, из-за своей недвижимой природы, тоже не всегда подходят.

                  На самом деле, проблема более фундаментальна, чем просто подсчёт ссылок на обекты в куче. Умные указатели в реальном коде решат часть ваших проблем — но далеко не все, и далеко не все решённые проблемы будут из числа самых коварных.


                  1. Satus
                    03.04.2017 17:01

                    А как вы предлагаете передавать объекты не «с потрохами»? Ссылки, из-за своей недвижимой природы, тоже не всегда подходят.
                    Не совсем понимаю. Допустим, у вас есть какая-то функция function(), которая у себя внутри вызывает функцию do_something( MyStruct* params ), где MyStruct — какие-то опциональные параметры в виде структуры. Что мешает мне сделать
                    void function()
                    {
                        MyStruct s;
                        // заполняем s
                        do_something( &s ); // или do_something( nullptr ), если не передаём ничего
                    }
                    
                    В чём тут может быть проблема? Или я что-то не понимаю?


                    1. TargetSan
                      03.04.2017 17:14

                      Проблема в том, что в общем случае вы не знаете, куда потом денется &s.


                      Естественно, что на простых примерах всё очевидно. Менее простой пример (тоже искусственный, конечно) — инвалидация итераторов:


                      std::vector<Foo> foos;
                      std::vector<Bar> bars;
                      auto const& first = foos.first();
                      for (auto const& bar: bars)
                      {
                          if(asFoo(bar) != first)
                              foos.push_back(asFoo(bar));
                      }

                      Ещё один пример (опять же псевдокод):


                      for(auto& item: range(returnVector()) | filter(userPredicate))
                      {
                          // Here, item's would point to nowhere, since initial container of returnVector is already dead
                      }

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


                      1. Satus
                        03.04.2017 17:55

                        В первом случае правила инвалидации итераторов детально описаны для каждой структуры данных. Тут проблема скорее не с висящими ссылками, а с неверным использованием структуры данных. Хотя я сам попадался на эту ошибку пару раз, но решать эту проблему добавлением runtime-оверхеда как-то сомнительно, когда её можно вполне решить compile-time проверками.

                        Второй случай интереснее. Потому что помимо классической reference to temporary встаёт вопрос, как работает filter и range. Если они делают копии, то проблемы не будет. Если нет, то опять таки, это можно проверить на этапе компиляции.


                        1. TargetSan
                          03.04.2017 18:28
                          +1

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


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


                          1. Satus
                            03.04.2017 21:20

                            Да я не то чтобы спорю. Я просто пытаюсь понять, что и зачем сделал автор этой статьи ;)


                            1. lexxmark
                              03.04.2017 21:33

                              Вот пример из GUI:

                              void Button::DoClick()
                              {
                                  // do some stuff
                                  ...
                              
                                  // emit signal
                                  click.invoke();
                              
                                  // do some other stuff
                                  // здесь this может быть удален каким-то обработчиком click
                                  // и мы упадем
                                  ...
                              }
                              


                              Что бы отловить проблему и понять кто удаляет нас можно написать:
                              void Button::DoClick()
                              {
                                  // мы должны жить до конца функции
                                  rsl::track::pointer<const Button, assert_on_dangle> guard(this);
                              
                                  // do some stuff
                                  ...
                              
                                  // emit signal
                                  click.invoke();
                              
                                  // do some other stuff
                                  // здесь this может быть удален каким-то обработчиком click
                                  // и мы упадем
                                  ...
                              }
                              


                              Вы какими средствами пользуетсь для решения подобных проблем?
                              Как уточнял коллега выше — shared_ptr не панацея.


                              1. Satus
                                03.04.2017 21:51

                                Удалять this изнутри себя, а потом обращаться по нему — баг в логике программы. Такое обычно решается тем, что удаление объекта откладывается до конца обработки события или до следующего фрейма. Все, кто писал на Qt, знают, что делает функция deleteLater() у QObject'a.


                                1. lexxmark
                                  03.04.2017 23:18

                                  Именно баг! Вопрос, как его ловить?

                                  deleteLater вещь хорошая, но она решает симптомы, а не причину. По сути это garbage collection — мы не знаем когда можем удалять объект, так давай те удалим его как-нибудь потом. Хотя признаюсь — сам иногда пользуюсь им.


                                  1. Satus
                                    03.04.2017 23:47

                                    Ловить можно с помощью таких утилит, как MemorySanitizer, valgrind, Dr.Memory и прочих. И просто взять за правило, что нельзя удалять объект, а потом к нему обращаться.
                                    deleteLater — не GC. Вы точно знаете, когда объект будет удалён — при следующем цикле обработчика событий.


                                    1. lexxmark
                                      04.04.2017 09:05

                                      Ловить можно с помощью таких утилит, как MemorySanitizer, valgrind, Dr.Memory и прочих.

                                      Про них я упомянул в статье со списком недостатков, по моему мнению.

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

                                      Это хорошое правило и самоочевидное правило. Так же можно посоветовать соблюдать ПДД чтобы никогда не попадать в аварии. Но мы то знаем, что жизнь сложнее и соблюдение правил не залог безопасности. Снизить вероятность можно, полностью исключить проблем — нельзя.

                                      deleteLater — не GC.

                                      Ну почему же, можно на него посмотреть с этой точки зрения. Вместо удаления объектов, которые нам не нужны, мы помечаем их как неиспользуемые и оставляем в памяти. Ни о каком RAII при этом речь уже идти не может (что также верно и для Java/C#).

                                      Вы точно знаете, когда объект будет удалён — при следующем цикле обработчика событий.

                                      Это половина правды: о). Согласно докам, объект, переданный в deleteLater может быть вообще никогда не удален.

                                      К тому же непонятно что будет если передать туда объект не созданный в куче? Лучше так не делать, это понятно, но как отличить если к вам в функцию пришел указатель на объект и непонятно надо/можно его удалять через deleteLater или нет.

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


                                      1. Satus
                                        04.04.2017 13:46

                                        Да, спасибо, теперь я всё понял.


                          1. lexxmark
                            03.04.2017 21:24

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

                            Когда такое только завезли в MS C++, реально посыпались ошибки в нескольких местах.

                            А в релизном режиме все превращается обратно в голые быстрые указатели без проверок.


                  1. lexxmark
                    03.04.2017 21:16

                    У shared_ptr есть один неизличимый оверхед — аллокация пямяти для счетчика ссылок. Такое решение в принципе несравнимо с решением без аллокаций.


                    1. Satus
                      03.04.2017 21:19

                      Чем так ужасна аллокация этого счётчика? Не так уж много памяти он занимает.


                      1. lexxmark
                        03.04.2017 21:42

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


                        1. Satus
                          03.04.2017 21:47

                          Ну так эта проблема решается make_shared.


                          1. lexxmark
                            04.04.2017 09:10

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

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


                    1. kmu1990
                      03.04.2017 21:30

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


                      1. lexxmark
                        03.04.2017 21:39

                        По поводу рекурсии согласен — можно сделать без нее.

                        Но вот с weak_ptr тоже не все в порядке — недавно была заметка на reddit, о том что weak_ptr не позволяют освободить память под объектом, если объект был создан с помощью make_shared (что активно советуют делать). И если объекты большие то мы получаем почти классический garbage collection — объекта уже нет, а память под ним освободится непонятно когда.


                        1. kmu1990
                          03.04.2017 21:51

                          1. Советуют или нет использовать make_shared, а голову еще никто не отменял, об этой проблеме известно давно, а даже если бы не было известно, то не трудно догадаться — сюрприза тут нет.
                          2. Я не говорил, что с weak_ptr нет проблем, мой поинт в том, что вы неправы насчет «несравнимости» подхода с дополнительной динамической аллокацией и без нее.
                          3. Еще одно подтверждение, что правильный дизайн и голова — лучшие друзья инженера. Умные указатели не освобождают от обязанности понимать, что происходит в коде, в частности, знать кто какими объектами владеет и чем ограничено время жизни объектов, все остальное лишь помошники в этом (возможно даже очень хорошие), но не замена голове.


                          1. lexxmark
                            04.04.2017 09:24

                            С этим трудно спорить — светлая голова лучше тёмной: о).

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

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

                            В статье описан, как раз один такой инструмент, со своими достоинствами и недостатками. мне кажется баланс достоинств и недостатков вполне приемлем: о)


                            1. kmu1990
                              04.04.2017 09:41

                              В статье описан, как раз один такой инструмент, со своими достоинствами и недостатками. мне кажется баланс достоинств и недостатков вполне приемлем: о)

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


                              1. lexxmark
                                04.04.2017 10:15

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

                                Так что боюсь само сравнение не имеет смысла.


                                1. kmu1990
                                  04.04.2017 10:25

                                  1. То что они изначально предназначены для разделенного владения не значит, что они не могут решать других задач. Если вас волнует имя, то using вам в помощь, если же вас интересует функциональность, то shared_ptr и weak_ptr обладают нужной функциональностью (по крайней мере я не видел, чтобы кто-то показал обратное).
                                  2. То что Бьерн, сказал, что ситуации когда дизайн программы требует использования shared_ptr из-за непонятного времени жизни программы должны быть сведены к минимуму (что несомненно правда), не значит, что мы не можем использовать shared_ptr/weak_ptr для отлова ошибок в коде.

                                  Нужно слушать не только что кто-то там сказал, но еще и почему кто-то так сказал.

                                  Вы предлагаете отлавливать dangling pointers — это это ошибочная ситуация, weak_ptr и shared_ptr позволяют ее отловить и являются стандартным средством языка. Так что сравнение как раз таки имеет смысл.


                                  1. lexxmark
                                    04.04.2017 21:21

                                    если же вас интересует функциональность, то shared_ptr и weak_ptr обладают нужной функциональностью (по крайней мере я не видел, чтобы кто-то показал обратное)

                                    Как я писал в статье, для отлова ошибок, гораздо интереснее момент, когда разрушается объект, на который есть ссылки, а не когда пытаются обратится по невалидному указателю. В этом случае shared_ptr/weak_ptr никак не помогут.

                                    не значит, что мы не можем использовать shared_ptr/weak_ptr для отлова ошибок в коде

                                    Мы не можем просто взять и добавить в существующий код shared_ptr, потому что если ваши объекты лежат на стеке или в unique_ptr или еще где-то, то нельзя их привязать к shared_ptr.
                                    Вернее можно, указав пустой deleter, но смысла в этом нет. Так как weak_ptr отловят окончание использования shared_ptr'ов, а не разрушение объекта.

                                    Вы предлагаете отлавливать dangling pointers — это это ошибочная ситуация, weak_ptr и shared_ptr позволяют ее отловить и являются стандартным средством языка.

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

                                    Так что сравнение как раз таки имеет смысл.

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

                                    1. Конструирование объекта: shared_ptr — аллокация; track::trackable — ничего.
                                    2. Создание новоро указателя: shared_ptr — инкремент, track::pointer — четыре присваивания указателей.
                                    3. Удаление указателя: shared_ptr — декремент, track::pointer — четыре присваивания указателей.
                                    4. Удаление объекта: shared_ptr — декремент, weak_ptr — деаллокация; track::pointer — проход по списку указателей с вызовом функции on_dangle.

                                    Вроде так.


                                    1. FlexFerrum
                                      04.04.2017 21:52

                                      Как я писал в статье, для отлова ошибок, гораздо интереснее момент, когда разрушается объект, на который есть ссылки, а не когда пытаются обратится по невалидному указателю. В этом случае shared_ptr/weak_ptr никак не помогут.

                                      На самом деле, интересно и то, и другое. Факт разрушения объекта, на который есть указатели ничего не говорит о том, где именно они «зависли».


                                  1. lexxmark
                                    04.04.2017 21:26

                                    На самом деле, если не заморачиваться с контейнерами, и перенести чать логики в track::trackable, то можно сделать Track::pointer гораздо эффективнее.


                    1. bibmaster
                      04.04.2017 19:31

                      Есть более дешёвый, но не стандартизованный intrusive_ptr. Но, конечно же без weak указателей. Странно что в boost не заточили на работу с такими указателям семейство intrusive контейнеров. В совокупности они хороши.


                      1. lexxmark
                        04.04.2017 21:27

                        intrusive_ptr вроде не подходит для отлова висячих ссылок.


  1. Gorthauer87
    03.04.2017 21:59
    +6

    Чего только люди не изобретают лишь бы не переходить на Rust.


    1. lexxmark
      03.04.2017 23:42

      Зачем в Rust unsafe?
      И как раст помогает в этих случаях?

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

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

      Уверен в Rust тоже редко возникают «мутные» места, которые нужно оборачивать в unsafe.
      И здесь Rust программисту ничем не помогает.


      1. TargetSan
        04.04.2017 08:54
        +3

        С++ — один сплошной unsafe блок. В Rust unsafe чётко локализован и легко поддаётся изоляции и верификации глазками.


        1. lexxmark
          04.04.2017 09:29
          +1

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


          1. TargetSan
            04.04.2017 10:19
            +1

            Не могу назвать себя спецом по Rust. Я скорее спец по С++, который интересуется Rust.
            По поводу UI — не могу ответить. Скорее всего, использовать то что есть через биндинги. Проекты есть, но мне их статус неизвестен.


          1. BlessMaster
            04.04.2017 15:54
            +3

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


            «Может ли быть удалена кнопка», которая «сейчас рассылает» (длительный процесс?) и «случайно» ли это произойдёт — это вопрос архитектуры системы. Вопрос в том, чего хочет её разработчик: иметь возможность удалить в произвольный момент времени или держать до последнего, заблокировав обновление интерфейса для других частей системы, пока кто-то ждёт ответа по сети или работает с диском.


            Но эта тема пока открыта и ждёт своих героев :-)


            1. lexxmark
              04.04.2017 21:29

              да… нигде нет счастья: о)
              как советуют выше — везде светлая голова нужна


        1. kmu1990
          04.04.2017 09:33

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

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


          1. TargetSan
            04.04.2017 10:43
            +4

            Начну с конца. Любым инструментом можно как сделать хорошо, так и сделать отвратительно.


            По поводу вашего студента — а вы уверены, что на С он бы написал лучше? У меня пока складывается впечатление, что он пытался "вбить молотком" своё решение, не сильно задумываясь о результате. И в С ему бы это удалось гораздо легче.


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


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


            • В С мы выигрываем в простоте самого языка, но платим когнитивной нагрузкой — всю семантику кода приходится тащить в голове.
            • В С++ мы платим сложностью языка и сложностью некоторых абстракций за простоту "клиентского" кода — только что-то уж очень часто эти абстракции текут.
            • В .NET/Java мы платим сложностью рантайма за более простое написание кода. Который "ломается" менее явно, а потому вызывает иллюзию большей надёжности.
            • В Rust мы платим наличием ранее неизвестных концепций и более строгой системой типов за более строгое разделение безопасного и небезопасного кода.
            • В том же Haskell мы платим наличием рантайма и непривычной парадигмой за ленивость и отделение побочных эффектов от всего остального кода.


            1. kmu1990
              04.04.2017 11:00

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

              По поводу вашего студента — а вы уверены, что на С он бы написал лучше? У меня пока складывается впечатление, что он пытался «вбить молотком» своё решение, не сильно задумываясь о результате. И в С ему бы это удалось гораздо легче.
              Касательно этого студента, конечно нет. Очевидно, что я бы не стал просить решать задание в двух экземплярах на Rust и на C. Но это лишь демонстрация того, что проблемы никуда не исчезнут магическим образом, потому что Rust.

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

              Кроме того я имел возможность посмотреть на других студентов, которые добровольно выбрали C, хотя большими знатоками C или C++ (или вообще дизайна кода) большинство из них не назовешь.

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

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


              1. DarkEld3r
                04.04.2017 11:49
                +3

                В частности unsafe магическим образом не будет локализован в коде, только потому что вы пишите на Rust.

                Почему нет? unsafe-блоки всё-таки будут явно выделены. Да, можно весь код в таком блоке написать, но и это имеется очевидное преимущество — сразу видно, что с кодом что-то не так. В плюсах какую-то хрень спрятать, в том числе случайно, проще.


                Студент тоже мне пел такие песни, но результат показал, что это только песни.

                Ну давайте делать далеко идущие выводы по тому какой код написал один студент.


                Я лично, все еще в ожидании хоть каких-нибудь конкретных доводов в пользу безопасности Rust

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


                Меня удивляют аргуметы о наличии unsafe: ведь и в C# и в джаве можно сишный код дёргать и нарушить имеющиеся гарантии, но почему-то никто не орёт, что гарантии менеджед языков — миф.


                1. kmu1990
                  04.04.2017 12:23
                  -1

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

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

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

                  Каких именно доводов? У языка есть хороший мануал, есть куча статей на эту тему — выводы сделать можно, если есть желание разбираться. Если нет, то о каких доводах можно говорить?
                  Да о каких выводах вы все говорите? У C++ тоже есть документация и есть статьи и еще больше чем про Rust. Куча этих статей о том как все делать правильно и тд и тп. Чем Rust в этом смысле отличается? Что вообще вы хотели сказать этим комментарием?

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

                  Обращу ваше внимание, что я не говорил, что иметь разделение на safe/unsafe в языке плохо, что программы вызывающие нативный код из managed языков безопасны и тд и тп. Это ваши слова, не мои — не нужно их мне приписывать.


                  1. TargetSan
                    04.04.2017 13:24
                    +3

                    Просто чтобы не потерять нить рассуждения.


                    Вы утверждаете, что Rust ничем не лучше С++, т.к. точно также позволяет при определённом желании наговнокодить, потоптаться по памяти и сделать другие плохие вещи. Более того, он с важей т.з. хуже т.к. накладывает дополнительные ограничения. Также он хуже, поскольку про него меньше статей, best practices etc. Ещё вам, похоже, надоели евангелисты, кричащие на всех углах, как Rust безопасен. Могу понять. Но главное пожалуй то, что для него гораздо меньше наработанного кода, библиотек и поддержки платформ. Если я где-то ошибся, поправьте.


                    Теперь попробую ответить на ваше утверждение, приведённое в формулировке выше.


                    1. Да, безусловно Rust позволяет сделать какую-нибудь бяку. Потому что язык для системного программирования смотрится мягко говоря странно без возможности прямой работы с памятью. Проблема немного не в том. В С++ требуются определённые усилия чтобы не допустить ошибку. Например, следить, куда и как передаём указатели и ссылки, вовремя смотреть, не "утечёт" ли указатель из вызванной функции в какую-нибудь глобальную переменную и т.п. Да, в тривиальных случаях это не проблема. Но кроме них есть нетривиальные случаи. В Rust при этом дополнительные сознательные усилия требуются чтобы написать неправильно — например, обозначить блок как unsafe, и в нём работать с указателями напрямую. Потому что ссылки даже внутри unsafe проверяются. При этом в документации вполне чётко написано, что unsafe только в случаях, когда вы точно знаете что делаете.
                    2. Да, Rust накладывает дополнительные ограничения. Как раз с целью избежать определённого класса ошибок. Удобно вам такое разделение или нет — вопрос к вам. Безусловно, у каждого свои требования.
                    3. Да, про него значительно меньше информации. Работа в этом направлении ведётся. Как раз на этот год планируется увеличить количество документации, описать основные практики — т.е. сделать язык "ближе к народу". На наработку информации нужно время. Все эти книги по С++ тоже появились не мгновенно в день релиза. Они постепенно появлялись в течение последних 25 лет, вместе с расширением базы пользователей.
                    4. Про евангелизм не буду. Отвечу про "магическую" безопасность. Она не магическая. Это просто устранение довольно широкого класса ошибок работы с ресурсами путём добавления дополнительных статических гарантий. И то, что для отключения этих гарантий требуются дополнительные телодвижения.
                    5. По сути, проблема та же что в пункте 3. Нужно некоторое время. Та же сборка под bare metal постепенно улучшается.

                    Повторю, если я где-то ошибся в обобщении ваших тезисов — поправьте, я постараюсь ответить.


                    1. kmu1990
                      04.04.2017 16:41

                      Вы утверждаете, что Rust ничем не лучше С++
                      Покажите мне, где я делал такое утверждение?
                      т.к. точно также позволяет при определённом желании наговнокодить, потоптаться по памяти и сделать другие плохие вещи.
                      Да позволяет, но оценку этому я не давал. Указал только на одну конкретную возможность поговнокодить, за которой компилятор не следит (просто потому, что за дизайном кода компилятор не следит).
                      Более того, он с важей т.з. хуже т.к. накладывает дополнительные ограничения.
                      Он накладывает дополнительные ограничения, и я видел примеры задач которые на C прекрасно решаются без них. Но оценку, что Rust хуже или лучше (опять же безотносительно области применения) я не давал.
                      Также он хуже, поскольку про него меньше статей, best practices etc.
                      Я говорил, что их меньше, но я опять же не говорил, что это делает его хуже или лучше. Более того, замечу что разговор про наличие литературы начал не я, и даже задал вопрос, каким образом это имеет отношение к делу, на который впрочем ответа не получил.
                      Ещё вам, похоже, надоели евангелисты, кричащие на всех углах, как Rust безопасен.
                      Такого я не говорил, но это правда (причем нет разницы между евангелистами Rust, C++, Java и даже попадавшимся мне на глаза евангелистом языка Ассемблера), потому что они почему-то очень любят другим приписывать утверждения, которых они не делали.
                      Но главное пожалуй то, что для него гораздо меньше наработанного кода, библиотек и поддержки платформ.
                      Это правда, но я об этом не говорил, и уж тем более не мог сказать, что из-за этого Rust плохой.
                      Если я где-то ошибся, поправьте.
                      Поправил.

                      Ответьте мне пожалуйства на следущий вопрос — может быть я правда чего-то не знаю о Rust. Каким образом Rust позволяет локализовать unsafe блоки в коде и не допустить чтобы они разбредались по всей кодовой базе?

                      Этот вопрос появился не на пустом месте. Потому что, если ваша программа упала и все что вам показывает coredump это покарапченную память. То первыми кандидатами на проверку как раз будут unsafe блоки в Rust (и те части кода, в которых делаются небезопасные вещи в языках где нет unsafe). Если по всей кодовой базе у вас будет торчать unsafe, то при поиске ошибок толку от того, что есть разделение на safe и unsafe код не будет.

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

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


                      1. TargetSan
                        04.04.2017 17:04
                        +3

                        Значит я вас таки неправильно понял. Отвечаю на поставленный вопрос


                        Ответьте мне пожалуйства на следущий вопрос — может быть я правда чего-то не знаю о Rust. Каким образом Rust позволяет локализовать unsafe блоки в коде и не допустить чтобы они разбредались по всей кодовой базе?

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


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


                        1. kmu1990
                          04.04.2017 17:14

                          Т. е. вы таки согласны с этим моим утверждением:

                          Он локализован, если инженер пишуший код его локализовал.
                          В ответ на ваш комментарий:
                          С++ — один сплошной unsafe блок. В Rust unsafe чётко локализован и легко поддаётся изоляции и верификации глазками.
                          Правильно?


                          1. TargetSan
                            04.04.2017 17:19
                            +1

                            Да. Согласен. Если код пишет идиот, который считает что компилятор/интерпретатор мешает его творческому полёту мысли, он может написать шлак на чём угодно, начиная с Asm и заканчивая Agda/Idris. Впрочем, до последних он скорее всего не дойдёт.


                            А где я вам противоречил?


                            1. kmu1990
                              04.04.2017 17:30
                              +1

                              1. Не стоит всех, кто делает ошибки в коде называть идиотами. Все когда-то начинали, но это не значит, что все были идиотами.
                              2. Вы нигде (кроме первого комментария, где вы сказали, что в Rust unsafe четко локализован, с чего все и началось), а вот DarkEld3r, похоже, сомневается в этом.
                              3. Кроме указания на то, что локализация unsafe-ов магическим образом в коде не появится от того, что код будет написан на Rust я вам тоже не противоречил.


                              1. DarkEld3r
                                04.04.2017 17:49
                                +3

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

                                Правильнее будет сказать, что (я считаю, что) в расте такие места проще найти и этого достаточно. Ведь нас не волнует абстрактная локализованность? Всегда есть определённые цели. Скажем, если мы ревьюваем код коллеги (или студента), то в С++ придётся внимательно всё просмотреть, а в расте если я увижу сплошные unsafe блоки, то отправлю переделывать. И так пока нормальная локализация не появится. (:


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


                                Собственно, остаётся только случай когда у человека нет понимания того как надо делать правильно и даже обильные unsafe его на мысль не наталкивают и при этом всём его никто не контролирует. Как по мне, это общая проблема обучения — надо понимать свой инструмент. Разница в том, что в С++ даже если есть понимание как писать надо, всё равно можно забыть проверить (и не важно unique_ptr, shared_ptr или обычный) указатель, а в расте случайно этого сделать нельзя — придётся сознательно написать unsafe. И не надо говорить, что профессионалы таких ошибок не делают — можно посмотреть на статьи о PVS Studio. (:


                              1. TargetSan
                                04.04.2017 17:50

                                Отвечал не на тот комментарий.


                              1. TargetSan
                                04.04.2017 18:04

                                Опять Хабр не туда постит комментарии… да что ж такое...


                              1. TargetSan
                                04.04.2017 19:44
                                +4

                                1. Не стоит, это правда. Но новички будут ошибаться просто от незнания инструмента. В список таких ошибок входит и неправильный дизайн. Но мы вроде говорим об инженерах, т.е. людях, которые хотя бы главу про unsafe прочтут в доке.
                                2. Неправильная формулировка с моей стороны. Правильная — "В Rust небезопасное поведение локализовано в unsafe блоках"
                                3. Хм, я вроде нигде не говорил, что unsafe сам магически локализуется.


                  1. DarkEld3r
                    04.04.2017 14:07
                    +3

                    И от того, что вы используете Rust магическим образом ваше сознание не изменится

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


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


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


                    Если вы умеете это делать вы сможете это сделать в C++, если вы не умеете этого делать, вы не сможете этого сделать и в Rust.

                    "Немного" не так. Есть ключевое отличие: в расте из коробки есть формальные метрики. И если я вижу unsafe, то начинаю смотреть на код внимательнее. Если unsafe слишком много — вполне себе повод отправить на переделку.


                    аргументация того студента и ваша аргументация идентичны

                    А какая у меня аргументация? Да, я считаю, что у раста есть ряд преимуществ (как и недостатков, впрочем), но приводить их, в очередной раз, смысла не вижу. Просто сделал комментарий конкретно про unsafe и на эту тему готов поспорить.


                    Что вообще вы хотели сказать этим комментарием?

                    Хочу сказать, что вопрос "какие у раста преимущества" провокационный. Вот как на это можно ответить? Если вкратце, то получатся сплошная агитация без фактов, если развёрнуто, то в комментарий можно не поместиться.


                    1. kmu1990
                      04.04.2017 17:01

                      «Немного» не так. Есть ключевое отличие: в расте из коробки есть формальные метрики. И если я вижу unsafe, то начинаю смотреть на код внимательнее. Если unsafe слишком много — вполне себе повод отправить на переделку.
                      Согласен, Rust не даст вам опустить unsafe и если кто-то кроме вас смотрит код и он достаточно хорошо разбирается в дизайне, то этот кто-то сможет вам на это указать.

                      Но продумать дизайн кода, так что бы unsafe не распространялся он за вас не сможет. И в этом мой поинт — если вы умете это делать, вы сможете это сделать и в C++ и в Rust, если нет, то вы не сможете этого сделать ни там ни там.
                      А какая у меня аргументация? Да, я считаю, что у раста есть ряд преимуществ (как и недостатков, впрочем), но приводить их, в очередной раз, смысла не вижу. Просто сделал комментарий конкретно про unsafe и на эту тему готов поспорить.
                      Прошу прощения, я не посмотрел на ник и перепутал вас с другим оппонентом и о его аргументации (цитата была в комментарии выше) я и говорил.
                      Хочу сказать, что вопрос «какие у раста преимущества» провокационный. Вот как на это можно ответить? Если вкратце, то получатся сплошная агитация без фактов, если развёрнуто, то в комментарий можно не поместиться.
                      Я такого вопроса не задавал. Я спросил, каким образом наличие или отсутствие литературы относится к делу. А объективных доводов пользу безопасности Rust я все еще не могу увидеть, основные причины:
                      — я видел код написанный на Rust, при этом имеющий проблемы работы с памятью, т. е. Rust не делает магии — хороший инженер будет хорошо писать на Rust (С++/Java/Haskell), плохой везде будет писать плохо (несколько слишком суровое обобщение, но оно должно донести суть идеи)
                      — кроме проблем работы с памятью есть и другие проблемы связанные с дизайном, логикой внешними зависимостями и Rust тут не помошник
                      — для борьбы с проблемами неправильного использования памяти существует некоторое количество разнообразных автоматических средств (тоже самое относится к проблемам связанным с конкуретностью)

                      На сколько, с учетом всего этого, переход на Rust при прочих равных условиях сделает жизнь лучше? Если хоть какой-то способ показать, что Rust реально улучшает качество? Когда я задаю такие вопросы, то получаю вот такие странные ответы (это реальная цитата из моей переписки про Rust):
                      Мне субъективно приятней писать на расте, чем на C++, благодаря красивому и лаконичному синтаксису, крутой системе типов, единообразию кодовой базы, отсутствию неявных преобразований, аккуратности во всем (например, гигиеничные макросы), крутым современным фишкам вроде классного паттерн-матчинга, очень хорошей системе сборки, expression-ориентированности, шикарному выведению типов, наличию большого числа статических проверок, ну и ОЩУЩЕНИЮ безопасности.


                      1. DarkEld3r
                        04.04.2017 17:35
                        +2

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

                        Не совсем понимаю, если честно. Вообще, кривая освоения языка у раста не самая плавная, но С++ на "нормальном" (а не базовом) уровне освоить тоже не просто. Собственно, я язык воспринимаю именно в разрезе когда у нас есть и опытные разработчики и новички — в таком случае первым будет проще разобраться с кодом вторых и объяснить что к чему, в том числе и в плане дизайна, ведь этому тоже надо учиться.
                        Кстати, можно вообще сделать запрет unsafe кода и компилятор за этим будет следить, что тоже полезно в плане разделения зон ответственности.


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

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


                        я видел код написанный на Rust, при этом имеющий проблемы работы с памятью

                        А я видел "утечки памяти" в менеджед языках — значит GC "не делает магии" (и не нужен)?


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


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

                        Аналогично. Не понимаю этот аргумент: да, решается определённая задача, а не "сделать хорошо", разве это значит, что она не нужна? Я бы ещё понял, если бы речь шла о том, что налогаемые ограничения нивелируют эти самые гарантии — тут ещё есть о чём поговорить, хотя опять же, легко скатиться в субъективщину.


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

                        Так вопрос тоже странный. Ведь "сделать жизнь лучше" можно очень по разному, совсем не только увеличением "безопасности и качества". Собственно, преимущества от языка и по моему мнению не исчерпываются дополнительными гарантиями. Если же всё-таки говорить о последних, то на мой взгляд, разница следующая: в С++ надо делать дополнительные телодвижения, чтобы получить безопасность, в расте — наоборот. Опять же, наступить на грабли UB сложнее.


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


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


                        1. kmu1990
                          04.04.2017 18:07

                          Не совсем понимаю, если честно. Вообще, кривая освоения языка у раста не самая плавная, но С++ на «нормальном» (а не базовом) уровне освоить тоже не просто.
                          Дело не в кривой освоения языка, дело в дизайне. Разделении на сущности, распределении обязанностей в коде, и тд и тп — это дизайн.

                          Кстати, можно вообще сделать запрет unsafe кода и компилятор за этим будет следить, что тоже полезно в плане разделения зон ответственности.
                          Условно вы получите Java, там не супер часто используются нативные binding-ы (за исключением таковых в стандартной библиотеке), и вот вам безопасное окружение (при условии, что мы доверяеме JVM и стандартной библиотеке).

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

                          Про литературу — это я опять же на вопрос о преимуществах языка отвечал, в общем случилось недопонимание.
                          Я задал вопрос о конкретных доводах в пользу безопасности Rust. На сколько реально улучшается качество кода при использовании Rust (я могу поверить, что если вы никогда не используете unsafe он достаточно безопасен для многих задач)? Потому что заявлений о безопасности Rust я слышал много (причем в виде Rust безопасен и точка), впрочем на сайте они прямым текстом пишут, что «Rust is totally a safe programming language» но только до тех пор пока вы не начнете использовать unsafe.

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

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

                          BTW, я посмотрел на список того, что считается UB в Rust, он не сильно то отличается от того, что считается UB, например, в C.


                          1. DarkEld3r
                            04.04.2017 18:28
                            +3

                            Дело не в кривой освоения языка, дело в дизайне.

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


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

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


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

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


                            На сколько это оправдано начинать проект на Rust, чтобы повысить «безопасность»?

                            Ответ всё тот же: зависит от проекта. Универсальных решений не бывает.


                            BTW, я посмотрел на список того, что считается UB в Rust, он не сильно то отличается от того, что считается UB, например, в C.

                            Да ладно? Можно взглянуть на список?


                            Подозреваю, что речь идёт о том, что можно сделать в unsafe: тогда мы опять возвращаемся к тому, что в С/С++ код является сплошным unsafe-блоком, а в расте такие блоки можно (и нужно) изолировать.


                            1. kmu1990
                              04.04.2017 18:41

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

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

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

                              Могу только повторить аргумент об автоматической проверке. Можно идеально продумать/задизайнить, а потом кто-то воткнёт reinterpret_cast (или лучше — сишный каст) и на ревью обо пройдёт незамеченным. Или забудет проверить нулевой указатель и т.д. Всё это не имеет отношения к дизайну и отлавливается растом.
                              А слово unsafe какое-то магическое и оно никогда не пройдет на ревью незамеченным?

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

                              Ответ всё тот же: зависит от проекта. Универсальных решений не бывает.
                              Абсолютно согласен и эту мысль пытаюсь донести до всех, кто считает, что может просто забыть про новые проекты на C и C++ и заменить их Rust-ом, потому что «безопасно».

                              Да ладно? Можно взглянуть на список?
                              Смотрите https://doc.rust-lang.org/reference.html#behavior-considered-undefined


                              1. DarkEld3r
                                04.04.2017 19:32
                                +2

                                то и в C++ не новичкам не так чтобы очень часто приходится сталкиваться с проблемами с памятью

                                Люди склонны ошибаться. В любом случае, мы переходим в область субъективных суждений: вряд ли, существует статистика как часто такие ошибки допускают. Могу снова предложить почитать статьи авторов PVS Studio и сделать поправку на то, что им попадает в руки код после ревью, прохождения тестов, а бывает, что и после других анализаторов.


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


                                Кстати, отсутствие дата рейсов тоже гарантируется.


                                разница в том, что компиялтор не может следить за этими запретами

                                Я изначально именно это и утверждал. Отсутствие автоматической проверки сразу обесценивает большинство "договорённостей".


                                А слово unsafe какое-то магическое и оно никогда не пройдет на ревью незамеченным?

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


                                BTW, как вы себе представляете такой запрет?

                                Например, новичкам нельзя лезть в модуль core, а в остальных запрет включён и да, компилятор там не разрешит unsafe.


                                Смотрите https://doc.rust-lang.org/reference.html#behavior-considered-undefined

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


                                1. kmu1990
                                  04.04.2017 19:53

                                  Люди склонны ошибаться. В любом случае, мы переходим в область субъективных суждений: вряд ли, существует статистика как часто такие ошибки допускают. Могу снова предложить почитать статьи авторов PVS Studio и сделать поправку на то, что им попадает в руки код после ревью, прохождения тестов, а бывает, что и после других анализаторов.
                                  И Rust это никак не исправляет, вводит новые сложности, которые устраняют возможность каких-то ошибок при определенных условиях, но на сколько эти ошибки влияют на качество проекта в целом, сколько новых ошибок будет из-за сложностей самого Rust-а — вопросы, которые остаются без ответа.

                                  Кстати, отсутствие дата рейсов тоже гарантируется.
                                  Только для ограниченного набора многопоточных примитивов. Потому как только вам нужно залезть в нетривиальный lock-free, реализовать SMR и тд, вам тут же приходиться использовать unsafe.

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


                                  1. TargetSan
                                    04.04.2017 20:43
                                    +1

                                    Вполне возможно, вы найдёте ответ на свой вопрос по этой ссылке:
                                    https://github.com/redox-os/redox
                                    Да, это пока исследовательский проект. Но эта ОС вполне себе работает. Ядро написано на голом Rust.


                                    1. kmu1990
                                      04.04.2017 21:08
                                      -2

                                      Это просто несерьезно… Когда redox будет использоваться кем-то кроме его создателей и для чего-то кроме развлечений, тогда можно будет сказать, что Rust показал себя в мире системного программирования. Да и вообще называть исследовательской ОС, вся новизна которой в том, что она написана на Rust — слишком громко.

                                      Даже элементарно, ребята в ядре используют топрный free-list алокатор с кучей ограниченной 256MB и эта константа забита в коде к тому же залоченный одним общим мьютексом. И это при том, что аллокация памяти довольно разнообразная на всякие интересные, быстрые и простые алгоритмы. Пожалуй перед тем как браться за ОС им стоит элементарно познакомиться с тем, что было сделано другими (серьезно, например, SLAB аллокаторы появились в SunOS, т. е. это не пипец какой bleeding edge, уж не говоря о том, что алгоритм там элементарный).

                                      Но на самом деле системное программирование не ограничивается только ядром ОС и писать полноценную ОС для этого не обязательно.

                                      Или вы этим комментарием хотели показать, что там нет unsafe? Там он есть, я это знаю наверняка.


                                      1. TargetSan
                                        04.04.2017 21:48
                                        +4

                                        А теперь объясните, чем для вас плох этот пример.
                                        На Rust? На Rust.
                                        Системный код? Системный код.
                                        Ансэйф изолирован? Изолирован, я смотрел.


                                        Или вам подойдёт только Unreal Engine, написанный на Rust для квантового компьютера и имеющий пользовательскую базу не менее 10 миллионов человек?


                                        1. kmu1990
                                          04.04.2017 22:18
                                          -2

                                          Эта ОС примитивный конкурентный код, где все защищено одним или несколькими несколькими глобальными локами (я даже не говорю про какие-то там lock-free, вопрос в элементарной конкуретности), а аллокатор памяти в этой ОС просто шутка. Этот код просто утрированный пример.

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

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

                                          И нет, мне не нужен Unreal Engine, уж точно не как показатель применимости кода для системного программирования. Совершенно точно квантовые компьютеры не имеют никакого отношения к делу. И о пользовательской базе в 10 миллионов человек я не говорил. И я еще даже не упоминал про такие вещи как QNX и L4* (которые создавались, в том числе, с расчетом на безопасность). Если вы перестанете утрировать, а перейдете к объективным аргументам, то обсуждение будет гораздо полезнее.


                                          1. TargetSan
                                            04.04.2017 22:34
                                            +4

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


                                            1. kmu1990
                                              04.04.2017 22:47
                                              -3

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


                                  1. DarkEld3r
                                    05.04.2017 10:01
                                    +1

                                    И Rust это никак не исправляет,

                                    Нет, (определённые ошибки) исправляет. Банально я не смогу забыть проверить указатель/optional.


                                    По какому это уже кругу мы пошли?..


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

                                    Ага.


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


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


                                    вам тут же приходиться использовать unsafe.

                                    И что? Если unsafe можно обернуть в безопасный интерфейс, то проблемы нет. Точно так же, как в С++ мы доверяем стандартной (и не только) библиотеке, так и в расте. С той разницей, что используя безопасный интерфейс выстрелить в ногу уже нельзя.


                                    Для системного программирования приходиться использовать unsafe

                                    Для системного программирования unsafe можно точно так же изолировать.


                                1. lexxmark
                                  04.04.2017 23:28
                                  +1

                                  Люди склонны ошибаться. В любом случае, мы переходим в область субъективных суждений: вряд ли, существует статистика как часто такие ошибки допускают. Могу снова предложить почитать статьи авторов PVS Studio и сделать поправку на то, что им попадает в руки код после ревью, прохождения тестов, а бывает, что и после других анализаторов.

                                  Соглашусь. Rust в этом смысле шаг вперед. Особенно если посмотреть на историю его создания.

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

                                  Может языку С++ стоит идти в эту же сторону, но с другой стороны — вставлять куски кода помеченные как safe, где будут работать строгие проверки как в Rust. И мы в своем коде будем понемногу расширять область safe: о)


                                  1. TargetSan
                                    04.04.2017 23:34
                                    +2

                                    Не выйдет. Семантика С++ конфликтует с такими проверками.


                                    1. lexxmark
                                      05.04.2017 10:16

                                      Не понятно.
                                      В Rustе можно иметь две семантики, а в С++ нельзя?


                                      1. TargetSan
                                        05.04.2017 10:54
                                        +3

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


                                        1. FlexFerrum
                                          05.04.2017 10:59

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


                                          1. TargetSan
                                            05.04.2017 14:36
                                            +3

                                            Дело именно в этом. Семантически такой "пустой объект" — всё ещё объект. К нему можно получить доступ, дёргать методы и т.п. Более того, С++ позволяет легко сделать ссылку-перемещение из простой мутабельной ссылки. В ряде случаев это может приводить к, например, неконтроллируемому появлению ссылок на элементы вектора, к примеру, которых уже давно нет на этом месте. Потому что исходный вектор был мало того что перемещён, а вдобавок реаллоцирован. В Rust такой перемещённый объект становится недоступен в принципе.


                                            1. FlexFerrum
                                              05.04.2017 23:03
                                              +1

                                              А вот с этого места по-подробнее. То, что после move'а объект должен переходить в состояние «инициализирован конструктором по-умолчанию» — это, в целом, логично и вписывается в идеологию языка. Ведь для объекта когда-нибудь будет позван деструктор. И точку вызова деструктора компилятор предсказать не может. Поэтому полностью разрушать объект в точке трансфера состояния — нельзя. А вот в каких сценариях появляются, как вы пишете, «неконтролируемые ссылки на элементы вектора» — мне очень интересно. Хотя бы один пример приведите, пожалуйста.


                                  1. DarkEld3r
                                    05.04.2017 10:20
                                    +2

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

                                    Какие-то подвижки в эту сторону уже идут: в C++ Core Guidelines вводятся новые типы (потенциально) с дополнительными проверками.


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


                  1. mellinare
                    04.04.2017 23:06
                    +4

                    Да этот код не будет помечен ключевым словом языка, но локализация такого кода — не задача компилятора, а задача инженера. Если вы умеете это делать вы сможете это сделать в C++, если вы не умеете этого делать, вы не сможете этого сделать и в Rust.

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

                    Зачем инженеру решать задачу (к тому же, весьма непростую), если есть автоматические способы ее решения? Компилятор Rust выдаст ошибку компиляции, если опасный, по его мнению, участок кода не будет обернут в unsafe блок. Если программист в своем уме, то тыкать unsafe тут и там просто так он не будет. Таким, вполе понятным образом, unsafe блоки будут локализованы. Если вы не решаете какую-то низкоуровневую задачу (написание ОС\аллокатора\структуры данных), то, скорее всего, unsafe вам вообще не понадобится.

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


                    1. lexxmark
                      04.04.2017 23:23
                      +2

                      Да. Rust хорош, особенно в воспитательных целях. Вместо преподавателя, по рукам дает компилятор.

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

                      На что был ответ — не всегда пожно перейти на Rust, поэтому комментарий про Rust вообще не в тему.


                      1. DarkEld3r
                        05.04.2017 10:43
                        +3

                        не всегда пожно перейти на Rust, поэтому комментарий про Rust вообще не в тему.

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


                    1. kmu1990
                      04.04.2017 23:23
                      -2

                      Под локалаизцией я понимаю не только то, что код будет обернут в unsafe блок (как это вообще можно называть локализацией?). Локализация, это когда ваш unsafe код сведен к минимуму и не размазан по всему коду. Как Rust поможет автоматически вам решить эту задачу?

                      Если вы не решаете какую-то низкоуровневую задачу (написание ОС\аллокатора\структуры данных), то, скорее всего, unsafe вам вообще не понадобится.
                      Простите, но в системном программировании примерно такие задачи и решаются.


  1. FlexFerrum
    04.04.2017 10:48
    +1

    Хм… Ваша статья написана несколько сумбурно (особенно вступительная часть), поэтому суть проблемы и решение понял, только прочитав статью дважды. И то не факт, что понял до конца. Теперь по сути. Как бы это странно не звучало, но лучший способ борьбы с dangling pointers — это не использовать «голые» указатели в коде вообще. Как, собственно, и рекомендуется в Core Guidelines. И да, действительно, в данном случае статический анализ + некоторая поддержка в рантайме может помочь больше, чем предлагаемые trackable-поинтеры. В том же GSL сейчас есть шаблонный класс owner, который просто показывает (статическому анализатору!) кто владеет объектом. Есть класс not_null, который гарантирует контракт, что хранимый в нём указатель гарантированно не будет nullptr. По такому же принципу можно добавить класс scoped_pointer, который будет гарантировать, что любая его копия умрёт раньше, чем указатель, из которого она создана и которым «временно владеет». Нарисовать такой класс, поддерживающий разные ситуации (но, очевидно, не все — инвалидирующиеся итераторы всё равно будут портить жизнь) — довольно интересная задачка.


    1. lexxmark
      04.04.2017 21:54

      Спасибо за развернутый комментарий.

      это не использовать «голые» указатели в коде вообще.

      Так вот нет невладеющих смарт указателей в стандарте (есть попытка observer_ptr).

      owner и not_null хороши, но мне кажется not_null это полиси к любому другому указателю, а не отдельный тип.
      чтобы можно было иметь owner_not_null, unique_ptr_not_null, shared_ptr_not_null и т.п.

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

      очень интересная задачка. есть мысли как можно реализовать эти гарантии? запретить копирование/перемещение?


      1. FlexFerrum
        04.04.2017 22:09
        +1

        owner и not_null хороши, но мне кажется not_null это полиси к любому другому указателю, а не отдельный тип.

        not_null можно инициализировать любым указателем (и обычным, и smart).

        очень интересная задачка. есть мысли как можно реализовать эти гарантии? запретить копирование/перемещение?

        Ну, по зрелому размышлению это трансформировалось немного в другое — в safe_ptr, который «стреляет» при доступе к разрушенному указателю или при попытке разрушить объект, на который ещё висят указатели. Идея (как я сейчас посмотрел по коду) очень близка к вашему trackable, только без разделения на отдельный trackable-класс и pointer. То есть если объект конструируется из указателя (одного из вариантов) — создаётся специфичный для указателя trackable. Если конструктором копий — то pointer. Всё это за одним фасадом. Список экземпляров, «вдалеющих» trackable, по большому счёту, имеет смысл только для debug-режима, чтобы иметь возможность отследить, где был совершён доступ к разрушенному указателю. На самом деле, для этого даже простой «скрытой» связки shared_ptr + weak_ptr хватит (ловить то надо факт лочки мёртвого shared_ptr). А для релиза достаточно счётчика ссылок и проверки, что при разрушении trackable счётчик равен нулю. Оповещать указатели о том, что объект сдох — бессмысленно. Это априори ошибочная ситуация, которая в «идеальном случае» должна закончиться AV.


        1. lexxmark
          04.04.2017 23:17

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

          ловить то надо факт лочки мёртвого shared_ptr
          гораздо интересенее момент разрушения объекта, на который есть ссылки.

          А для релиза достаточно счётчика ссылок и проверки, что при разрушении trackable счётчик равен нулю. Оповещать указатели о том, что объект сдох — бессмысленно.

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


          1. lexxmark
            04.04.2017 23:40

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


            1. FlexFerrum
              05.04.2017 00:13

              Что именно имеется в виду?


              1. lexxmark
                05.04.2017 08:12
                +1

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


                1. FlexFerrum
                  05.04.2017 09:19

                  Хм. Спасибо. Не знал.


          1. FlexFerrum
            05.04.2017 00:13

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

            Но это не тот safe_ptr, который я имел в виду. Это какой-то интрузивный смарт-поинтер со списком владения. Свой я сейчас набросаю.

            гораздо интересенее момент разрушения объекта, на который есть ссылки.

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

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

            Вот, кстати, фишка с контейнерами из статьи вообще не ясна. Что именно и к чему хочется прикрутить? Какой сценарий?


            1. lexxmark
              05.04.2017 08:37

              Вот, кстати, фишка с контейнерами из статьи вообще не ясна. Что именно и к чему хочется прикрутить?

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

              Какой сценарий?

              Ну например какой-то кеш, который может инвалидороваться:
                  std::map<key, cached_value_type> cache;
                  cached_value_type* get_value(key)
                  {
                      // создать кеш значение для ключа, если его еще нет
                      ...
                      return cache[key];
                  }
              

              да и везде, где мы передаем элемент контейнера вовне.

              Заменяем std::map на rsl::track::map и тип возвращаемого указателя с cached_value_type* на rsl::track::pointer<cached_value_type>, и вуаля — при инвалидации или удалении кеша мы ловим повисшие ссылки на него.


              1. FlexFerrum
                05.04.2017 09:56
                +1

                С контейнерами все грустно. Как мне кажется, вы пытаетесь предоставить гарантий больше, чем у вас вообще есть. То, что у вас trackable привязан к аллокатору, а не к аллоцированному объекту делает его фактически бесполезным. map и list аллоцируют память под каждый элемент. Вектор — пачками. Дека — чанками. При этом все они имеют массу оптимизаций, из-за которых отследить фактическую инвалидацию элемента в любых случаях — невозможно. Вы вот в тестах на vector проверяете, что при erase хвостовые указатели инвалидируются, а не то, что объект, на который указывает удалённый указатель, становится невалидным. Согласитесь, это разные вещи. Код, в котором есть указатель на удалённый объект, может быть очень "удивлён", что значение объекта внезапно сменилось. Это допустимо с голыми указателями, но такого не ожидаешь от смарт-указателей которые, вроде как, от этого гарантируют. Поэтому да, в моем представлении время жизни trackable-объекта должно быть всегда меньше, чем того объекта, на который он указывает. И не важно — на стеке этот объект, в контейнере или в динамической памяти.


                1. lexxmark
                  05.04.2017 10:38

                  из-за которых отследить фактическую инвалидацию элемента в любых случаях — невозможно

                  странно, мне казалось все просто. какой объект разрушается, такие указатели и не валидны.
                  я понимаю, что семантически после удаления элемента в векторе, все последующие элементы перемещаются вправо, но по факту элементы валидны, кроме последнего. вы также ничем не гарантируете, что у объекта на стеке не вызвался оператор перемещения и теперь это «немного» другой объект.
                  Контейнер/не контейнер — без разницы. Гарантий нет.

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

                  Честно говоря никогда о таком не слышал. Мне кажется это очень субъективное мнение.

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

                  У вас время жизни объекта ограничивается деструктором и оператором перемещения, как вы сможете реализовать свой SafePtr в таком случае?
                  Обязать клиента поддерживать эти инварианты?


                  1. FlexFerrum
                    05.04.2017 10:56

                    странно, мне казалось все просто. какой объект разрушается, такие указатели и не валидны.
                    я понимаю, что семантически после удаления элемента в векторе, все последующие элементы перемещаются вправо, но по факту элементы валидны, кроме последнего. вы также ничем не гарантируете, что у объекта на стеке не вызвался оператор перемещения и теперь это «немного» другой объект.
                    Контейнер/не контейнер — без разницы. Гарантий нет.

                    Эм… Нет. Это немного разные вещи. Помещение safe-указателя в скоуп времени жизни указываемого объекта как раз таки гарантирует, что никто (ну, ок, почти никто :) ) не влезет и ничего не поменяет. То есть вы не можете с SafePtr на стековый объект снять копию и использовать её после выхода за скоуп указываемого объекта — у вас стрельнёт либо ассерт на локе, либо вылетит исключение. Есть кейсы с многопоточностью, когда какой-то поток может «сбоку» что-то там подправить в контейнере, но это уже типичный data race и надо лечить именно его.

                    Аналогично с контейнерами — ситуация, когда вы снимаете с элемента контейнера указатель или итератор, а потом этот элемент инвалидируете — в целом, ошибочная. То есть ваш код должен быть написан так, чтобы исключить такие вещи. А не помогать их «мягко» обойти. :) А приведённый в комментарии выше пример с кешем — там в map'е лучше сразу trackable-указатели хранить (или как-то так), чтобы дать клиенту гарантию, что удаление элемента из контейнера инвалидирует всё, что связанно именно с этим элементом.

                    У вас время жизни объекта ограничивается деструктором и оператором перемещения, как вы сможете реализовать свой SafePtr в таком случае?
                    Обязать клиента поддерживать эти инварианты?

                    Не совсем понял вопрос.


                    1. lexxmark
                      05.04.2017 11:08

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

                      Не совсем понял вопрос.


                      То есть технически надо сделать так, что бы мы «стреляли» не только при удалении объекта, но и при перемещении/копировании в него нового объекта.
                      Вопрос как это реализовать?


                      1. FlexFerrum
                        05.04.2017 11:39

                        Это тонкий семантический вопрос. :) В случае стековых объектов/объектов в динамической памяти всё, вроде, ясно — вы объявляете переменную (или создаёте объект по new), и у вас появляется некая сущность, над которой можно выполнять операции. Чаще всего — полностью контролируемые вами. Если вы хотите что-то сделать с таким объектом — вы (чаще всего) сделаете это явно — присвоите ему новое значение, скопируете, выполните метод и т. п. Таким образом, safe (или trackable) указатели на такие объекты имеют строго определённую семантику: они контролируют доступ к явно объявленному объекту, что бы потом с ним не происходило. А не к занимаемой объектом памяти (которая идёт как-бы прицепом).
                        А с объектами в контейнерах — хитрее. В контейнерах мухи — отдельно, котлеты — отдельно. Память распределяется аллокатором и потом контейнер обращается с ней так, как ему заблагорассудится. «Захотел» — разместил в ней один объект, «захотел» — переразместил другой. «Захотел» — вообще отволок объект в другое место. Таким образом выходит, что указатели, которые в первом случае контролировали доступ к объекту, который занимает какую-то память, в случае с контейнером начинают контролировать доступ к памяти, которую занимает какой-то объект. То есть несколько, гм, меняют свою семантику. Поэтому в случае контейнеров я бы вёл речь о стабильных итераторах, которые либо «указывают» на объект (элемент контейнера), на который были получены, либо явным образом инвалидируются и их невалидность можно проверить. С моей точки зрения, это более точно соответствует существующим контрактам контейнеров.


                        1. lexxmark
                          05.04.2017 20:43

                          Возможно это просто полиси в trackable, которое указывать как интерпретировать перемещение/копирование объекта: либо должны инвалидироваться ссылки, либо нет.

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


                          1. FlexFerrum
                            05.04.2017 23:23

                            Возможно и policy. Но я бы предпочёл какое-то однозначное поведение.

                            А этот SafePtr — да, берите. Я себе ещё сделаю. :)


          1. FlexFerrum
            05.04.2017 01:12

            В общем, мой вариант SafePtr'а «в лоб» (на базе std::shared_ptr/std::weak_ptr) — здесь. Аккуратно стреляет при обращении к протухшему указателю. Без каких либо оптимизаций и специализаций под std::shared_ptr/std::unique_ptr. Чисто proof of concept.

            И да, не пугайтесь — это C++17. :)


            1. lexxmark
              05.04.2017 09:09

              любопытно…

              я смотрю m_refs а с ним и AddRef/Release вообще не нужны там: о)

              вы делаете неявное предположение, что SafePtr<T, detail::OwnerTag> должен жить столько же, сколько и объект, на который указывает.

              По сути можно выкинуть TrackableBase и Trackable.
              Переименовать SafePtr<T, detail::OwnerTag> в Trackable, и оставить там shared_ptr<Trackable, empty_deleter>, который проинициализировать this.
              A SafePtr<T, detail::ReferenceTag> переименовать просто в SafePtr[T] и иметь указатель на T и weak_ptr[Trackable].

              Идея ясна. Более эффективно убрать shared_ptr/weak_ptr и продублировать их функциональность с помощью std::atomic_int, что вы, наверное, и хотели сделать. Таким образом мы убираем аллоцирования в shared_ptr.

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

              И да, не пугайтесь — это C++17. :)

                 SafePtr(T* obj)
                      : m_trackable(new detail::Trackable(obj))
              

              Шо, make_shared до сих пор не завезли???: о)

              Кстати, safe_ptr от electronics arts мне совсем не понравился.


              1. FlexFerrum
                05.04.2017 09:38

                Отвечаю без цитат, ибо с мобилы.


                Да, то, что такой SafePtr живёт в скоупе жизни указываемого объекта — это основное предположение. Собственно, именно за счёт него и можно предоставить все необходимые гарантии. Но из-за того, что указатели бывают shared — придётся в этом коде сделать ряд приседаний, чтобы именно в этом случае SafePtr мог спокойно пережить выход за скоуп owner'а.


                AddRef/Release воткнул для Release-версии, чтобы owner просто считал количество ссылок на него. Если при разрушении не 0 — помирать с исключением в деструкторе (привет дяде Александреску и его folly).


                Схлопнуть до того количества типов, о котором вы говорите — не выйдет. Причины три: ковариантность простого указателя и указателя на константу, ковариантность указателя на базовый и на производные типы, а также специализация для shared_ptr. То есть в случае, если owner конструируется от shared_ptr, то reference-версия должна стать этаким синонимом weak_ptr для этого shared'а. В остальных случаях — указателем на потроха owner'а. Возможно, что в traceable вообще type erasure втыкать придётся.
                Про работу с контейнерами — напишу отдельно. Там всё грустно, на мой взгляд. А make_shared в стандарт завезли, куда ж без него. :) Я имел в виду автоматической вывод типа при конструировании.


                1. lexxmark
                  05.04.2017 11:01

                  Но из-за того, что указатели бывают shared — придётся в этом коде сделать ряд приседаний, чтобы именно в этом случае SafePtr мог спокойно пережить выход за скоуп owner'а.

                  Здесь непонятно, что имелось в виду. Хотя для shared_ptr вообще SafePtr не нужен, просто для полноты?

                  AddRef/Release воткнул для Release-версии, чтобы owner просто считал количество ссылок на него. Если при разрушении не 0 — помирать с исключением в деструкторе (привет дяде Александреску и его folly).

                  Похоже вы что-то недописали в коде. или я что-то не понимаю, тут ничего не будет падать:
                      ~SafePtr()
                      {
                          auto tracker = m_tracker.lock();
                          if (tracker)
                              tracker->Release();
                      }
                  


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

                  ну это я на основании вашего «proof of concept», сколько там ковариаций, столько и у меня: о)

                  Возможно, что в traceable вообще type erasure втыкать придётся.

                  как я говорил, для shared_ptr такие указатели как SafePtr вообще бесполезны — вся функциональность и так есть из коробки. По простому можно просто запретить создавать SafePtr от shared_ptr. Ну или сделать независимую специализацию trackable и SafePtr для shared_ptr и не объединять код с применением type erasure


                  1. FlexFerrum
                    05.04.2017 11:28

                    Здесь непонятно, что имелось в виду. Хотя для shared_ptr вообще SafePtr не нужен, просто для полноты?

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

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

                    Стрелять должно в деструкторе owner-версии SafePtr, которого в примере действительно нет. :D

                    как я говорил, для shared_ptr такие указатели как SafePtr вообще бесполезны — вся функциональность и так есть из коробки.

                    Тут речь о консистентности. Когда клиент указывает в параметрах метода SafePtr — он не знает, с каким именно указателем этот метод будет вызван. То есть мы получаем некую абстракцию над указателями, которая гарантирует, что объект либо будет жив в момент доступа к нему, либо (если объект внезапно умирает) в программе будет не UB на доступе к разрушенному указателю, а вполне конкретное падение.


                    1. lexxmark
                      05.04.2017 20:34

                      Тут речь о консистентности.

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


                      1. FlexFerrum
                        05.04.2017 23:08

                        А вот это — смотря с какой стороны смотреть. Если формулировать контракт так, что SafePtr даёт безопасный доступ к объекту до тех пор, пока объект жив. Для scoped-указателей этот доступ заканчивается вместе со скоупом. Для shared — пока жив shared. С точки зрения клиентского кода совершенно не важно, как был получен pointer. Важно, чтобы он был «жив».


      1. FlexFerrum
        04.04.2017 22:25
        +1

        А, да. owner определяется как:
        templateusing owner = T;

        То есть это просто синоним типа, но «видимый» на этапе статического анализа. То есть можно написать owner<unique_ptr> и спокойно пользоваться.


        1. lexxmark
          04.04.2017 23:19

          да, понял. и not_null и owner хорошо комбинируются с другими указателями.