LINUX.ORG.RU

познать Rust

 


2

6

Доброго времени суток. Ищется материал по Rust, где:

1. В сжатой, но понятной форме изложено положение Rust в контексте других языков. В чём суть его новизны. В качестве базы ожидается C++, Java, Common Lisp. Желательно, с примерами кода.

2. Критика. Какие грабли уже проявили себя.

Желательный объём - до 5 страниц текста.

★★★★★

Ответ на: комментарий от anonymous

У тебя глаз замылился

Наверное, ты прав.

Хотя про «развесистый матчинг» всё-таки не совсем согласен. Если хочется обработать каждую ошибку отдельно, то разве с исключениями иначе будет? Будет «развесистый catch» на каждый случай. И точно так же - если устроит общий случай, то есть _.

все больше и больше костылей для простой вообще то задачи

Но всё-таки. Как по твоему, решить задачу просто и изящно?

DarkEld3r ★★★★★ ()
Ответ на: комментарий от DarkEld3r

Дык, что тут является первопричиной? Точно так же джавист скажет, что он обходится без RAII и with-... макросов. Привычка.

Джависты-то как раз жаловались, что после перехода с С++ у них потекли ресурсы из за отсутствия RAII, вчера читал. А вот что является первопричиной - не скажу, пока сам не понимаю.

относительно finally и using «для ресурсов» у деструкторов есть одно преимущество - если добавить в класс поле, в деструкторе которого надо что-то освобождать (то есть и внешний обьект становится ресурсом), то всё заработает автоматически, без изменения кода

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

Когда в С++ не удаётся решить головоломку конструирования, делаешь поле указателем (а сначала хотел полем или ссылкой), и в соломке грабельки образуются. Либо, если начитаться книжек по архитектуре, начнёшь дробить объект на более мелкие, и это уже не будет похоже на «ничего не надо менять». Напротив, менять придётся очень и очень много.

до тех пор, пока не создано две версии - одна защищённая, другая нет

Как это?

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

den73 ★★★★★ ()
Ответ на: комментарий от DarkEld3r

Но всё-таки. Как по твоему, решить задачу просто и изящно?

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

Пункт 1. Есть атрибут переменной const, есть где-то, скажем, mutable. А надо добавить спец. атрибут «обязательно прочитать после каждой записи», который выдаст ошибку компиляции при нарушении этого правила.

Также, каждая ф-я, возвращающая код ошибки, должна для этого получать параметр по ссылке (или по имени). Т.е., прикладной программист будет вынужден завести переменную, в которую будет записан код возврата, и передавать её. Разработчик библиотеки должен сделать параметр с атрибутом «прочитать после записи». Компилятор может проверять соответствие типа.

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

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

Пункт 3. Вместо типа «возврат либо код ошибки», который в Расте надо каждый раз дополнительно определять или обрабатывать, в С имеем просто две переменные «возврат» и «код ошибки», которые между собой никак не связаны. А значит, их пару, уникальную для данного вызова или для данной функции API, не нужно рассматривать и обрабатывать как нечто единое - достаточно просто написать код, который с ними работает. Думаю, что это проще.

Как-то так.

Пп 2 и 3 направлены на объяснение, почему в Расте сложнее, чем в С. А п 1 - это предложение, как сделать так же просто как в С и при этом так же надёжно, как в Расте.

den73 ★★★★★ ()
Последнее исправление: den73 (всего исправлений: 3)
Ответ на: комментарий от den73

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

Хм... о чём речь? На более-менее конкретном примере можно? Были упомянуты ссылки - ну так это ограничения именно ссылок (они должны быть в валидном состоянии), к конструкторам это напрямую не относится.

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

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

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

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

DarkEld3r ★★★★★ ()
Ответ на: комментарий от den73

Т.е., в С любой указатель - это на самом деле «дармовой» тип Option и все об этом знают.

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

Опять же, в расте основное удобство как раз в том, что ты не можешь «не проверить указатель» (вернее Option). То есть, ни в результате рефакторинга, ни из-за недостатка опыта, ни «спьяну» нельзя про это забыть. Как по мне, это круто и полезно. Как бонус - семантика указателя и опционального значения разделены.

Причём к похожим идеям много где приходят. «Даже» в джаве появился Optional.

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

Честно говоря, не совсем понял. В расте не надо «каждый раз определять» типы. Есть шаблонные Option/Result. И работа с ними будет однообразная как раз. Если хочется более сложных вещей, то это уже ближе к исключениям. В том смысле, что ошибки представляют разные типы и несут с собой разную дополнительную информацию. И как «нечто единое» их сделать не получится. Ну если «старые добрые» коды возврата тебя не устраивают.

DarkEld3r ★★★★★ ()
Ответ на: комментарий от DarkEld3r

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

Хм... о чём речь? На более-менее конкретном примере можно?

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

class TRICKY {
  some_class a;
  other_class& b;
public:
  TRICKY(some_class i, other_class &j) : a(i), b(j) 
Естественно, при полном доступе на запись к исходникам иерархии классов это можно обойти, но такой доступ не всегда имеется. И если не задано достаточно удобных конструкторов для some_class и other_class, то ситуация может стать безвыходной.

Опять же, в расте основное удобство как раз в том, что ты не можешь «не проверить указатель» (вернее Option).

С этим я не спорю.

Я ищу ответ на вопрос, почему написанный на Расте код обработки ошибок столь страшен.

И вижу причину в enum-ах. Если отделить возврат и ошибку (передавать её хоть через указатель, как бывает в С), то будет проще, мне кажется. Но это нужно пробовать и проверять.

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

den73 ★★★★★ ()
Последнее исправление: den73 (всего исправлений: 3)
Ответ на: комментарий от den73

Если отделить возврат и ошибку (передавать её хоть через указатель, как бывает в С)

То мы потеряем все преимущества.

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

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

Речь о том, что инициализировать some_class и other_class где-то может быть «неудобно»? Ну такой контракт у них. Да, классы могут быть написаны неудачно, но не понимаю как контекст или возможность заводить переменные тут поможет.

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

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

DarkEld3r ★★★★★ ()
Ответ на: комментарий от DarkEld3r

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

А разве ты не это предлагал, говоря о двухфазной инициализации?

den73 ★★★★★ ()

Тренд не читал. Подскажите, в статье на хабре пишут, что в Rust ожидается «новое выражение try ... catch, которое позволяет перехватывать и обрабатывать исключения».

Правильно я понимаю, что скоро в Rust добавят исключения, как в C++ и у всех иесключениехэйтеров бомбанет?

makoven ★★★★★ ()
Ответ на: комментарий от makoven

Правильно я понимаю, что скоро в Rust добавят исключения, как в C++ и у всех иесключениехэйтеров бомбанет?

«Нет» на оба вопроса.

tailgunner ★★★★★ ()
Ответ на: комментарий от tailgunner

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

makoven ★★★★★ ()
Ответ на: комментарий от makoven

try..catch как-бы предпологает, что можно устанавливать обработчики исключений где-то выше по стеку

try и catch - это просто слова. Их семантика определяется языком (если определяется). В случае Rust их предлагаемая семантика описана в профильном RFC, которого ты не читал.

tailgunner ★★★★★ ()
Последнее исправление: tailgunner (всего исправлений: 3)
Ответ на: комментарий от den73

А разве ты не это предлагал, говоря о двухфазной инициализации?

Я говорил о том, что это можно сделать, если хочется. Как общий подход оно мне как раз не нравится.

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

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

DarkEld3r ★★★★★ ()
Ответ на: комментарий от makoven

Но ведь try..catch как-бы предпологает...

В том предложении try/catch - сахар для тех же Result. Не знаю зачем это называют обработкой исключений, а не ошибок.

DarkEld3r ★★★★★ ()
Последнее исправление: DarkEld3r (всего исправлений: 1)
Ответ на: комментарий от tailgunner

try и catch - это просто слова. Их семантика определяется языком (если определяется). В случае Rust их предлагаемая семантика
описана в профильном RFC, которого ты не читал.

И я не читал. Где можно прочитать?

den73 ★★★★★ ()
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от DarkEld3r

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

den73 ★★★★★ ()
Ответ на: комментарий от tailgunner

Спасибо. Вот интересно. У меня есть вычислительный алгоритм, который обсчитывает много независимых точек. И в нём есть такая строчка:

a=b+c*d;
В ней может возникнуть две ошибки, связанных с переполнением. Я хочу, чтобы программа записала в журнал внятное сообщение об ошибке с указанием номера строчки, в котором произошла ошибка и продолжила обсчитывать остальные строчки.

Допустим, я создам под каждую точку свой «таск» (что-то меньше треда, т.к. создавать тред дорого).

Допустим, я с помощью #line (не знаю, есть ли он в Расте) смогу вывести код ошибки. Вопрос: могу ли я в сегодняшнем Расте оставить эту строчку как есть или я должен разбить её на две, чтобы проверить ошибку отдельно для сложения и отдельно для умножения?

den73 ★★★★★ ()
Ответ на: комментарий от den73

Вопрос: могу ли я в сегодняшнем Расте...

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

thread '<main>' panicked at 'arithmetic operation overflowed', <file>:7

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

P.S. Line есть.

DarkEld3r ★★★★★ ()
Последнее исправление: DarkEld3r (всего исправлений: 1)
Ответ на: комментарий от DarkEld3r

А как же будет, если я перемножу два больших флоата?

(я хочу такой же #line для лиспа; потому что в SBCL навигация по сорсам перманентно глючит)

den73 ★★★★★ ()
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от den73

А как же будет, если я перемножу два больших флоата?

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

DarkEld3r ★★★★★ ()
Ответ на: комментарий от DarkEld3r

Тогда с флоатами проблема решена. Сделать а+б*в, а потом проверить результат. Но с целыми числами всё не так уж хорошо. Хотя при легковесных задачах паника выглядит более-менее адекватным решением.

А чем, если не секрет, им не угодили обычные исключения? Тем, что у них динамическая область видимости?

den73 ★★★★★ ()
Ответ на: комментарий от den73

И кстати, правильно ли я понял этот RFC, что с точки зрения потока управления catch - это грубо говоря, метка, на которую можно передать управление конструкцией "?" В отличие от try!, которая умеет только return из функции.

Т.е. по сути отличие от try! лишь в том, что можно остаться в пределах той же функции?

den73 ★★★★★ ()
Последнее исправление: den73 (всего исправлений: 1)

Тэкс :-) И тут про цепепе :-) Ну-ка, глянем :-)

anonymous ()
Ответ на: комментарий от eao197

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

А давай так, в каком другом языке, кроме ущербненького цепепе или дельфи есть деструкторы? :-) Ты хоть знаешь, почему их дедушка добавил в цепепе? :-)

anonymous ()
Ответ на: комментарий от Legioner

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

Надо же :-) Кто-то уже начинает что-то подозревать :-)

anonymous ()
Ответ на: комментарий от eao197

Нет, похоже, тупы не все Lisp-еры, а только чмо аноним со смайликами, которое который приходит уныло срать в камменты во все темы про Rust, D и C++.

Вау :-) Меня обсуждают матёрые гуру цепепешники, с несколькими десятками годов рефакторинга цепепе по стилю «из пустого в порожнее» за плечами :-) Как это мило :-)

anonymous ()
Ответ на: комментарий от Legioner

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

Не-а. :-) Правильный подход в Common Lisp с его сигнальным протоколом, или продолжения в Схемах :-)

anonymous ()
Ответ на: комментарий от eao197

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

Так а в цепепе всё - «серьёзная проблема, не имеющая простого решения» :-) Простое там сделается сложно, а сложное - не делается вообще :-) Вон, какой-то аноним хвастался, что он аналог Guile сделал на C++14, что как бы намекает :-)

anonymous ()

Common Lisp

Каким бы хорошим или плохим он бы ни был, но когда слышишь такие слова: «Now that I'm not paid to write Common Lisp code anymore», «Common Lisp is not very welcoming nor attractive to new blood» от олд-скульных матёрых лисперов, известных всему сообществу, то закрадывается мысль, что у CL будущего мало, мягко говоря :-)

anonymous ()
Ответ на: комментарий от eao197

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

Отлично! :-)

anonymous ()
Ответ на: комментарий от anonymous

Простое там сделается сложно, а сложное - не делается вообще

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

anonymous ()
Ответ на: комментарий от den73

Но с целыми числами всё не так уж хорошо.

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

А чем, если не секрет, им не угодили обычные исключения?

Я не читал все-все RFC и, тем более, их обсуждения, так что могу в какой-то степени ошибаться. Подозреваю, что аргументы примерно такие: акцент языка на явности и «надёжности» - возвращаемое значение явно видно и явно же требуется обработать, в отличии от исключений. Плюс исключения утяжелят рантайм, а минималистичный (и отключаемый) рантайм - одна из фич языка.

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

DarkEld3r ★★★★★ ()
Ответ на: комментарий от den73

Т.е. по сути отличие от try! лишь в том, что можно остаться в пределах той же функции?

И в том, что обработку ошибок можно сделать в одном месте, а не размазывать равномерно. (Модифицированный) пример из RFC:

try {
    foo()?.bar()?.baz()?
} catch {
    FooErrorA(val)  => ...,
    FooErrorB(val)  => ...,
    FooErrorC(val)  => ...,
    BarError(val)  => ...,
    BazError(val)  => ...,
}

DarkEld3r ★★★★★ ()
Ответ на: комментарий от DarkEld3r

Что-то оно прямо-таки один в один из swift'а скопировано.

Gorthauer ★★★★★ ()
Ответ на: комментарий от Gorthauer

Что-то оно прямо-таки один в один из swift'а скопировано.

Swift не знаю, погуглил - у них там do-блоки, да и catch больше на «традиционный» похож, то есть отдельные catch на каждый тип, а не catch как match в расте. Других различий в нюансах тоже хватает. Общее только то, что в обоих языках вместо исключений используется возврат значений, правда в свифте оно засахарено в throws.

DarkEld3r ★★★★★ ()
Ответ на: комментарий от DarkEld3r

Т.е. по сути отличие от try! лишь в том, что можно остаться в >>пределах той же функции?

И в том, что обработку ошибок можно сделать в одном месте, а не >размазывать равномерно. (Модифицированный) пример из RFC:

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

А не является ли это решение аналогом checked исключениям в Яве, которые, как я понял, не особо взлетели?

den73 ★★★★★ ()
Ответ на: комментарий от den73

Не вижу разницы. С обычными исключениями...

Я отвечал на вопрос в чём разница с «try!».

А не является ли это решение аналогом checked исключениям в Яве, которые, как я понял, не особо взлетели?

Checked исключения являются, по сути, сахаром над возвратом Result. Так что неким аналогом обо будет даже при отсутствии в расте своих try/catch.

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

DarkEld3r ★★★★★ ()
Ответ на: комментарий от DarkEld3r

Мнение из книги Боба Мартина «чистый код» - они не взлетели. Я тоже не пользуюсь Java. Думаю, дело всё же в статической области видимости. Исключения имеют динамическую и тем самым противоречат идее языка с максимумом статических проверок.

den73 ★★★★★ ()
Ответ на: комментарий от anonymous

когда слышишь такие слова: «Now that I'm not paid to write Common Lisp code anymore», «Common Lisp is not very welcoming nor attractive to new blood» от олд-скульных матёрых лисперов, известных всему сообществу, то закрадывается мысль

Лузеры и слюнтяи, пускай поплачутся в уголке, лол.

Oxdeadbeef ★★★ ()
Ответ на: комментарий от den73

Там наезды в основном стилистические. Просто исключения плохо подходят для обработки отказов (тогда catch надо писать прямо тут же, что раздувает код), коды возврата плохо подходят для всего, а писать свои Result в Java как-то не принято. Ещё в Java нет удобного сахара для преобразования между checked и unchecked. Так что выходит легче просто бросать unchecked и писать аккуратно.

ilammy ★★★ ()
Ответ на: комментарий от ilammy

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

Нет книги под рукой, но ЕМНИП написано, что они не прижились в народе, это просто было отмечено как явление природы. Были ли попытки объяснить, почему так случилось - я не помню. Помню фразу «время споров прошло».

den73 ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.