Pull to refresh

Comments 73

Таки-меня коробит, что для такой важной и распространенной вещи как обработка ошибок по всему коду используется макрос, который еще и меняет поток выполнения. Не то что бы я видел в этом какой-то вот прямо практический вред, нооо как-то криво это. Ожидаю рано или поздно какого-то обобщенного решения интегрированного в сам язык, вроде же были какие-то rfc.
RFC есть (https://github.com/rust-lang/rfcs/pull/243), но оно не движется.
Автор не хотел вставлять неявный вызов `Into::into` в `?`, человек из core team с ним не согласился, так всё пока и заглохло, потому что особо и не горит. Но рано или поздно будет, можно их попинать в комментах для ускорения.
Коробит, потому что непривычно? Макросы в Rust реализованы достаточно неплохо для того, чтобы быть хорошим инструментом для решения более-менее повседневных задач, не вызывая при этом боли, которая может возникнуть например в C++. Тем более, обработка ошибок через try! получается гораздо короче, чем использование try-catch блока в тех же сценариях.

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

try! спасает только то, что он всего один, достаточно прост и быстро узнается в процессе изучения языка.
Я думаю, именно потому, что вызовы макроса могут вести себя не так тривиально, как вызовы функции, они обязательно требуют специального обозначения вроде foo!. Это что-то вроде «будь внимателен, здесь происходит что-то необычное».

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

try!, println!, format!, vec!, assert! и assert_eq!

Наверное можно было бы расширить синтаксис языка специльно для исключений, но такое расширение все-равно будет следовать логике макроса try. Например, что-то вроде foo(try bar()). Для остальных случаев есть более универсальная конструкция match, которая уже и так умеет все то, что в обычных языках умеет try-catch блок. Единственное отличие – match работает для каждого конкретного вызова, в то время как try-catch может оборачивать целый блок кода. Но я бы поспорил о том, что это недостаток для языка программирования, который делает ставку на надежность.
Ну как, я относительно давно на ржавчине пытаюсь писать, так что дело не просто в непривычности.

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

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

Повторюсь, каких-то жутких практических недостатков в данном конкретном случае я не вижу — согласен с Михаилом, что ситуацию спасает одиночество `try!`. Но мне было бы комфортнее, если бы вышеуказанный RFC довели до ума и реализовали, пускай оно даже один в один как `try!` работало бы. Или даже что бы таки реализовали HKT и ввели в язык do-нотацию :).
А как код выглядел бы с do-нотацией? Не получится ли слишком громоздко?
Полагаю как-то так: ideone.com/DlGYHp со строки 32.
Всё, что выше — просто чтоб компилировалось.
В общем-то да, примерно то же самое.
> А как код выглядел бы с do-нотацией?

Сейчас есть накостыленый на макросах mdo! — https://github.com/TeXitoi/rust-mdo — лучше без HTK особо не сделаешь. С ним как-то так выходит:

fn write_to_file_using_try() -> io::Result<()> {
    let mut file = try!(File::create("my_best_friends.txt"));
    try!(file.write_all(b"This is a list of my best friends."));
    println!("I wrote to the file");
    Ok(())
}

fn write_to_file_using_mdo() -> io::Result<()> {
    mdo! {
        mut file =<< File::create("my_best_friends.txt");
        ign file.write_all(b"This is a list of my best friends.");
        ign Ok(println!("I wrote to the file"));
        ret Ok(())
    }
}


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

> Не получится ли слишком громоздко?

Может, от конкретного кода зависит. Функции, возвращающие чего-то левое (как println! в примере), которые не выходит вынести за рамки do, конечно, придется «втягивать».

Если что, я на знатока функциональных буррито не претендую, но идея кажется заслуживающей проработки)
Я Все хотел задать вопрос, Вот Rust И GO, высоконагруженные платформы, А есть ли succes story от их использования,
Я например хочу написать веб приложение, что лучше использовать ??(Best practices)? у Го и Rust есть предпосылки для роста, это не NodeJs со своей лапшей (Callbacki)
New York Times вроде как переписали кучу всего на Go
https://www.youtube.com/watch?v=bAQ9ShmXYLY
Можно здесь подсмотреть github.com/golang/go/wiki/GoUsers
То есть, Go используется дофига где. Насчёт применения Rust в продакшене пока рано говорить.
Кроме OpenDNS чего не слышал. И то обвязочка для сишной либы, вы серьёзно называете это продакшеном?
Логика на уровне «я не слышал, значит этого нет». А я слышал про Skylight и MaidSafe, и чего, кто теперь прав?

Да, это боевой код, который работает в реальных условиях — это production. Его размер здесь не важен, но если хотите, то вот цифры (это честные SLOC, без пробелов и комментариев):

У MaidSafe всё ядро распределённой сети на Rust (14000 строк).

Redox — это активно разрабатываемая ОС на Rust. 40000 строк кода на Rust. И пока вы не возразили, что этим «никто не пользуется» или «вы про это не слышали» — вспомните, как начинался Linux.
Логика на уровне, что в списке про Go присутствует индустрия. А ваш список — карта песочниц в округе.

Его размер здесь не важен, но если хотите, то вот цифры (это честные SLOC, без пробелов и комментариев)
Вообще говоря важен, особенно не в абсолютных цифрах, а в степени вовлечённости в архитектуре. MaidSafe ещё не релизнулся, вы не компетентны если называете opens-source поделку до первого RC «продакшеном». 14000 LOC это смешно, у меня хобби-проект на Go за полтора месяца вышел на 8000. 14000 с учётом специфики задач это уровень концепта.

Redox — это активно разрабатываемая ОС
Я не буду вас ловить и проверять, насколько активно и какие там люди (больше, чем полтора ботана, надеюсь). По моему опыту разработки модулей и драйверов Linux — раст там вообще ничем не поможет. А до продакшена этой поделке лет 5 в лучшем случае, 20 в обычном.

Вы поймите, продакшен это коммерчески успешные проекты, когда люди за свои ошибки отвечают жопами, а код прибывает по 10к в неделю минимум. Когда можно набрать 5 человек с улицы и вовлечь в проект меньше, чем за полгода. Ваш список это хороший индикатор, но вы оффтопите в этом треде про продкшен.
На Skylight-то посмотрите, прежде чем обвинять в некомпетентности.
А что там? Главный разработчик растовых хаков для Skylight — Yehuda Katz, который и написал эти две статьи в блоге про то, какой Rust офигенный, он же входит в rust core team. Думаете, я случайно про «полтора ботана» пошутил?
Деньги зарабатывают? Зарабатывают. Нормально для языка, который стабилизовался вот ещё меньше, чем полгода назад.
Вы меня не слышите.
больше, чем полтора ботана, надеюсь
Линус вон вообще один начинал, в чём проблема-то?

Да и, что меня ловить-то, я и сам поймаюсь.

Redox SLOCs


А code churn на уровне 200 000 — 2 000 000 строк это уже извините меня не «поделка», как ни крути.

А до продакшена этой поделке лет 5 в лучшем случае, 20 в обычном.
Сколько лет пройдёт до того, как махровая индустрия возьмёт на вооружение Rust обсуждать есть смысл только с учётом того, что Go появился на 3 года раньше Rust. Прямые сравнения неуместны.
Ребят, ну чего вы как индусы числом строчек меритесь? :-)
продакшен это коммерчески успешные проекты, когда люди за свои ошибки отвечают жопами, а код прибывает по 10к в неделю минимум.


Можно узнать источник этого определения?
То есть, к определению
боевой код, который работает в реальных условиях — это production
товарища mkpankov у вас вопросов нет? Или вы согласны, что он неадекват и интересуетесь именно этим определением? Что-ж почитайте вот jdevelop.blogspot.ru/2013/03/blog-post.html
Нехорошо людей за глаза неадекватами называть.
Практически нет вопросов. Можно уточнять что такое «боевой», что такое «реальные условия», но в целом вопросов нет.
Я например хочу написать веб приложение, что лучше использовать
Ruby on Rails, потому что быстрее и проще, а узкие места переписать на Go всегда успеете. Всё равно их переписывать придётся.
Best practices для Rust пока нет.

История успеха — это Servo. Он рисует страницы в 2 раза быстрее Gecko в одном потоке и ещё быстрее в многопоточном режиме.

А из коммерческих пользователей есть как минимум Maidsafe, Skylight, OpenDNS. У них в разных местах есть посты о том, почему они выбрали Раст.
История успеха — это Servo.

Заинтересовал, погуглил. Наткнулся многократно на то, что его пока не собираются использовать в Firefox. Задумался ещё больше. Ведь разработка такой большой вещи занятие весьма эпохальное (это работа не для 5 программистов). С какой целью тогда они его делают?
Не совсем так.

Вот план реализации Servo: github.com/servo/servo/wiki/Roadmap. Моё понимание таково: Servo пока просто не готов к боевому применению. Хотя некоторые сайты уже отрисовываются так же хорошо, как и в Gecko.

Насколько я помню, в целом Mozilla действует так: постепенно заменяют компоненты в Firefox на те, что используется в Servo, так что в конце они будут использовать одно и то же, а затем можно и движок заменить.
В нём уже все спецификации реализовали или только те, что можно быстро нарисовать? :-)
это не NodeJs со своей лапшей (Callbacki)

Лапша у вас на ушах, уважаемый. Вы внимательно погуглите магические 'javascript promise', 'javascript async/await', 'javascript es2015', чтобы глупостей не говорить.
Это конечно все хорошо, просто отладка осложняется, и код не очевиден, Я хотел уже осваивать Node.JS но потом увидел что на код надо смотреть не линейно а сначала код и заглушка(Callback) которая после нее. Конечно я понимаю таково преимущество Асинхронности, может быть проблема старого поколения что на код они смотрят линейно ???
javascript es2015

Его V8 уже поддерживает стабильно?
К сожалению очень сложно представить выгоду от всех этих сложностей языка в реальных проектах. Там где нужен жесткий real time или крайне стабильный драйвера Rust наверно и крут. Но вот касательно всех других проектов — думаю для массового рынка он будет слишком невыгоден скоростью разработки.
Почему-то все очень любят оценивать только скорость разработки, забывая про скорость отладки. И проблема в том, что эти две величины связаны между собой не так просто, как хотелось бы: почти любой многопоточный алгоритм значительно сложнее отлаживать, чем идентичный ему однопоточный, при этом оба могут быть записаны в одну строку (а-ля records.map(processFn) и records.parallelMap(processFn)). В языках без GC к этому добавляются проблемы использования объектов после разрушения и т.п.

Баги в программе — это ещё и удар по репутации. Уж лучше потратить в два раза больше времени на написание кода, чем написать код в два раза быстрее, а потом столько же его отлаживать (ну, это как повезёт), вдобавок теряя клиентов и получая негативные отзывы.
Уж лучше потратить в два раза больше времени на написание кода, чем написать код в два раза быстрее, а потом столько же его отлаживать (ну, это как повезёт), вдобавок теряя клиентов и получая негативные отзывы.


Нельзя сказать однозначно. Как минимум, отлаживать нужно не всегда.
Вам никто ведь не мешает делать unwrap везде, а затем постепенно рефакторить. Стабильность теряется разумеется.
Распространенность в стандартной библиотеке возвратов структуры Result вместе с рекомендованным способом его обработки через макрос try! (который по сути пробрасывает ошибку вверх по стеку вызовов) весьма напоминает эмуляцию throw exception из Java и подобных языков.
Нечто вроде эмуляции. Эксепшены более тяжеловесны, так как в них (как минимум) сохраняется весь stack trace. (Про другие причины их тяжеловесности в разных языках я когда-то читал, но уже многое подзабыл, и ссылку на статью найти не могу).
Похожая эмуляция исключений, кстати, недавно появилась в swift 2.0, и меня это очень сильно порадовало. Все же не очень удобно и читабельно плодить кучу if-ов для проверки ошибок после вызова каждой функции. Но решение в Rust мне видится более гибким, так как можно делать всякие функциональные чудеса с помощью map, map_err и проч., полностью отказавшись от try! при необходимости.
Порадовало отсутствие стектрейса? Вы таки мазохист?
Можно автору статьи несколько вопросов?

Какой размер имеет в Rust экземпляр типа i32? В Си i32 – элементарный тип. Скорее всего в Rust i32 тоже элементарный, т.е. не класс. Поэтому его размер, скорее всего, 4 байта. И какой размер имеют экземпляры типов Option и Result<i32, E>? И ещё вопрос – указатель в Rust является элементарным типом или нет? И каков его размер?

Как обработать в Rust возникающие при сложении переполнения, т.е. ошибки?
Автор статьи вам скорее всего не ответит, поскольку не читает Хабр. Отвечу я как переводчик.

i32 – элеменентарный тип, его размер 4 байта. В Rust вообще нет понятия класса.
Размер Option<T> равен размеру T + дискриминант (обычно 1 байт) + выравнивание.
Размер Option<&T> равен размеру указателя для вашей архитектуры. Дискриминант не нужен, поскольку значение None кодируется как нулевой указатель. В Rust нет нулевых указателей, так что сам &T никогда не будет равен null.
Размер Result<T, E> равен размеру дискриминанта (1 байт) + размеру наибольшего возможного значения (или размер T, или размер E) + выравнивание.

Арифметические операции по умолчанию игнорируют переполнение, но если вам нужно их обрабатывать, можно воспользоваться методами checked_add, checked_mul и т.д. В случае с i32 они возвращают результат Option<i32>.
Простите, я вас обманул по-поводу переполнения:

Обычное сложение 150u8 + 150u8 паникует в случае переполнения (и корректно завершает поток).
Метод 150u8.checked_add(150u8) возвращает Option<T> со значением None.
Метод 150u8.saturating_add(150u8) возвращает граничное значение (255).
Метод 150u8.wrapping_add(150u8) игнорирует переполнение (44).
На данный момент, если не ошибаюсь, паникует только в отладочной сборке. Это может быть важно)
Да, точно, для release оператор + ведет себя аналогично wrapping_add.
Если Option имеет дискриминант (или дескриптор, т.е. описание? В языке Алгол-68 применялся термин «паспорт», этот паспорт дополнительно описывал объекты), то из функции нельзя вернуть Option в 32-разрядном регистре. Некоторая потеря эффективности в пользу надёжности.
Установил Rust себе на Windows XP – не работает. Потом узнал, что эта платформа не поддерживается. Тогда поставил на 32-разрядную Windows 7. Компилятор сообщает о синтаксической ошибке на «hello world», взятый из учебника. Наверно, нужен Linux… Компилятор Rust выдаёт не исполняемый код, а код в LLVM IR. А под Windows единственный инструмент, который превращает код LLVM IR в исполняемый – Visual C++. Как-то не по фэншую – сочетание свободного ПО и проприетарного… Или я неправильно информирован?
> Установил Rust себе на Windows X
Но сегодня 2015 год же!

> А под Windows единственный инструмент, который превращает код LLVM IR в исполняемый – Visual C++. Как-то не по фэншую – сочетание свободного ПО и проприетарного… Или я неправильно информирован?
Если бы вы реально хотели посмотреть, то увидели, что у раста два варианта под windows. Через MSVC и Mini-GW.

> Компилятор сообщает о синтаксической ошибке на «hello world», взятый из учебника.
Может програмирование это не для вас?
Ни к чему нападать на человека, который заинтересовался языком, задаёт нормальные вопросы и пытается разобраться.
Человек не задает нормальные вопросы. Человек язвит и не совсем понимает, что же такое этот LLVM.

Задавал бы он нормальные выпросы, он бы показал код и ошибку. И точно бы не требовал, чтобы компилятор работает на 32 разрядной ХР.
Некоторая потеря эффективности
Я не могу понять, в чем потеря эффективности? Не сущесвует никакой возможности впихнуть тип «i32 или ничего» в 4 байта. По-любому нужен еще хотя-бы один бит, чтобы кодировать это «ничего». Как раз для этого необходим дискриминант. Никто вам не мешает возвращать из функции просто i32, размером в 4 байта.

На правах безумной идеи: тегированная память, как в Эльбрусах, была бы весьма кстати.
Я не могу понять, в чем потеря эффективности? Не сущесвует никакой возможности впихнуть тип «i32 или ничего» в 4 байта.

В Си эта проблема решалась значениями-исключениями
hFile = CreateFile(...);
if (hFile == INVALID_NAHDLE_VALUE) ...


Никто вам не мешает возвращать из функции просто i32, размером в 4 байта.

и потерять всю эту безопасность, паттерн-матчинг и вернуться на ступень назад
> В Си эта проблема решалась значениями-исключениями

И это печально.
Не понимаю зацикленной логики. Да, в Си это часто решалось значениями-исключениями. Да, это было быстрее. Но это менее безопасно. Сделали более безопасно. Нам не нравится что это стало медленнее. Так что же мы изначально хотели, быстро или безопасно?
Мы хотели бескомпромиссно, в этом идеология си/си++
Если для этого нужно усложнять концепцию, вставив в описание Option ручное указание значения-исключения для NONE, такое усложение стоит выигрыша в скорости.
Это не «бескомпромиссно», это небезопасно.

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

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

Наверное плохо объяснил. Всё остаётся по-прежнему, и возвращается Option, но компилятору даётся хинт, что значение INVALID_HANDLE_VALUE трактуется как None, таким образом результат помещается в int32
Такой хинт уже реализован в Rust для типа Option&lt&T>. Такой тип занимает 4 байта (для 32-битной архитектуры), и значение 0 трактуется как None.
Да, это интересная идея. Я думаю, что сейчас это требует поддержки в LLVM, поэтому сложно делать это для любых типов.
Разве тут что-то требуется от LLVM? Сейчас для реализации хака с Option<&T> достаточно обернуть данные в обертку NonZero. Можно себе представить, что нечто подобное можно сделать для произвользного диапазона значений. Но это все начинает напоминать оптимизации в стиле «давайте хранить 3 разных значения в двух байтах». Можно, но стоит ли?
Я попробовал посмотреть на разницу в LLVM IR для ссылки и указателя: is.gd/EyTrrF

Как видим, ссылка явно dereferenceable. Я думаю, что для LLVM указатели с возможностью обнуления — особый случай, и они специально поддержаны. Обобщённого же механизма нет. Да и в core только NonZero, а общего типа с возможностью указать, какие значения особые, нет.
А давайте поговорим о том, как приведенный вами пример позволяет узнать причину ошибки?
В качестве примера, в glibc есть костыль с глобальной errno. Про многопоточность естественно тут приходится забыть.
Сам я за подход rust. Обычно накладные расходы не столь критичны. А если уж очень надо — можно как в C сделать, кто мешает то…
Про многопоточность естественно тут приходится забыть.

errno давно уже объявляется как __thread (для MSVC __declspec(thread))
у каждого потока своя переменная errno.
Под многопоточностью я подразумеваю возможность прокинуть прозрачно ошибку в другой поток и обрабатывать ее там.
В rust, очевидно, можно передать Option и Result<T, E> из одного потока в другой совершенно прозрачно.

P.S. если уж говорить про errno, то много где(если не везде) это не переменная.
как приведенный вами пример позволяет узнать причину ошибки?

пример скорее был для Option, а не Result, а с Result можно сделать хинты, мапящие разные ошибки на константы, которые не может вернуть ф-ция (например, если возвращается pointer таких недопустимых результатов очень много)
Насколько я знаю, единственным «специальным» значением для pointer является 0 (т.е. null). Все остальные значения могут быть валидными указателями. А вот специально для null в компиляторе Rust уже реализован хак (ответил вам выше), оптимизирующий размер Option<&T> до размера указателя.
Компилятор сообщает о синтаксической ошибке на «hello world», взятый из учебника.
Какая ошибка у вас?
Компилятор Rust выдаёт не исполняемый код, а код в LLVM IR.
Это не так, компилятор Rust как исполняемый файл rustc выдаёт исполняемый код. LLVM IR используется внутри и скрыто от пользователя. Visual C++ используется не здесь, а при компоновке с системными библиотеками и нужен если вам нужно MSVC ABI — из всего Visual C++ нужен лишь link.exe.
Sign up to leave a comment.

Articles