LINUX.ORG.RU

познать Rust

 


2

6

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

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

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

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



Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от 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
()
Ответ на: комментарий от 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
()
Ответ на: комментарий от 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
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.