Pull to refresh

Comments 314

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

Вопрос: на фрилансе rust вообще как, восстребован? Посматриваем куда уйти, смотрели на java/c#/c++, но сложилось ощущение, что эти языки только суровый офисный энтерпрайз.
Спасибо. За рекламу мне ее платили. Я напишу в Раст что они должны :-)

Во фрилансе я раста вообще не видел. Это все-таки суровый язык.

В начале статьи был упомянут голанг — чем он не устроил? тот же standalone бинарник, скорость и отсутствие жопоразрывания borrow checker-ом — код пишется если не просто, то хотя бы без большого напряга. Если захотелось бы всякого нестандартного — можно было бы попробовать собрать проект через tinygo и получить похожий по размеру бинарник как и в расте.

Как говорится в очень старом и добром Скетч Шоу (https://m.vk.com/video-155258957_456239019):


Но я не с вами флиртую!


Хотелось попробовать и выучить раст. Посему я решил что голанг для этой цели не очень подойдет.

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

Я, к сожалению, даже вакансий чисто на Rust не видел. Так что до фриланса он, может, лет через 10 доберётся. Если повезёт.

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

Спасибо, душа радуется )

Интересно написано. Я начинал серьёзно программировать ещё на asm i8080 (вернее КР580ВМ80А), нравилось байтики и даже битики считать. А потом покатился по С->С++->Perl->PHP с периодическими шарпами-джавами-нодами и т. п… На хлеб с маслом хватает, но часто скучать начал по тем временам. Вот чаще появляться стала мысль в качестве хобби какую-то экзотику изучить, чтоб было интересно. Начал Irdis, но не очень зашло — оторванно как-то от жизни. На rust имеет смысл писать что-то что обычно пишут на PHP/Java/C# веба с основной целью перехода — радоваться циферкам в htop? Или это на порядки больше времени?

Зря вы так про идрис, это хаскель с завтипами done right. Его основная проблема не в оторванности от жизни, а в нулевой экосистеме. Возможно, через N лет станет лучше. Ну и ещё оно мозги прикольно выворачивает, начинаешь по-другому относиться к типам. По крайней мере, для меня идрис после хаскеля был примерно как хаскель после обычной императивщины.


Возвращаясь к расту — забавно, как раз в эти выходные в порядке развлечения пролистал rust by example, кстати. По впечатлениям — хочется писать на нем то, что иначе писал бы на плюсах, где нужна максимальная близость к железу. Вебню с PHP/Java/C# писать на нем смысла не вижу, на хаскеле это делать приятнее и прикольнее.

Веб на хаскеле? Так действительно делают?

Делают. На прошлый проект к нам пришел дев, ушедший с проекта, где веб на Хаскале делали.

Ну хаскель-код для меня как китайский текст почти ( А экосистема и есть приближенность к жизни.

Просто потому что вы даже не пробовали по-настоящему.

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

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

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


Поведу себя грубо и скажу, что это _преувеличение_ для красного словца.

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

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

Паскаль был обычный. Сложности — ну вот так у меня видимо мозг был устроен, я НЕ ПОНИМАЛ этого, никак. Первым ступором который занял у меня неделю наверное было i := i + 1. Если бы была сишная запись с обычным "равно" я бы наверное и две просидел. "как i может быть равно самому себе и ещё единице? Это бессмыслица". Понял, дальше пошло-поехало, с циклами особых бед не было, а потом появились массивы и я снова не мог с ними совладать. Но, в итоге понемногу вошел в ритм и дальше особых проблем не было аж вплоть до ООП.

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

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

В общем скорее непохоже на годный пример того, что «новые концепции всегда сложны». Просто есть простые концепции, а есть сложные (и иногда цена не оправданна).

Хаскель я точно не пробовал.

Не-не. Основная причина в том что бинарный файл получается близко к тому что вы на ассемблере писали. Радоваться htop поэтому можно, но еще можно и дрова писать на расте.


А еще можно веб серверы пилить. Actix-web прямо там и есть.


А еще можно заниматься абсолютно новомодным webassembly.


Ну и тут внезапно Microsoft увлеклась растом. Посему можно гуглить rust winrt.


Да и в ядре Линукса стало ржаветь потихоньку.

Таки уже или все еще в процессе обсуждения?

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


Системное программирование как таковое, драйвера, ядра, инфраструктура мало уже интересует.

Внезапно? То есть то что они 5 лет оплачивали Николаю Киму фуллтайм разработки актикса (и это только то что я достоверно знаю) это тогда что?)

На rust имеет смысл писать что-то что обычно пишут на PHP/Java/C# веба с основной целью перехода — радоваться циферкам в htop?
Если вы разрабатываете главную страницу Google – то да, стоит. В остальных случаях это не оправдано. Цена изменения кода на Rust сильно высокая, что идет в противоречие с «эджайл» стилем разработки типичного веба.

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

Можете пояснить, что имеется в виду под «ценой изменения кода на Rust»?

Разрабатываем самые обыкновенные веб сервисы, которые можно было бы делать и на Java, например.

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

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

За 3 недели раст выучить? Круто! Я уже с год разбираюсь, и то куча вопросов. Правда, в свободное время, когда руки доходят, но всё же.

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

Ага особенно пробираться через trait objectы с async методами и хитрыми лайфтаймами. Ничего сложного

Спасибо, очень актуальная для меня статья! Как раз эти выходные провел за изучением Раста. Ощущения очень сходные. То же возникли ощущения что читать книгу надо было в другом порядке.

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


Но конечно это идет вразрез с маркетологовским "главное завлечь любыми средствами, а потом человеку будет неудобно менять инструмент и он останется". Есть же rust by example где как раз по идее более дружелюбно и "продажнически" преподносится как надо писать. А растбук это скорее справочник для тех кто знакомится с языком. Благодаря этому я весь растбук прочитал ну часов за 10 чистого времени. Сколько времени читается средняя книжка по го-питону с тонной воды и где не объясняется каким образом магическая магия работает думаю не надо говорить.

Справедливости ради, в го магии не то, чтобы очень много :)
Если не считать ей рантайм со сборщиком мусора и шелудером, конечно.

Кстати был крайне удивлен, что в языке с ГЦ в 2020 году можно получить УБ на ровном месте, но вот можно.


А магией можно считать даже стандартные типы, по крайней мере пока го 2.0 не выйдет.

А где вы в го наткнулись на UB?
И в чем магия стандартных типов? Мне казалось, там всё максимально просто и явно сделано.

Вот код с УБ


package main

import "fmt"

func surprise(a []int) {
    a = append(a, 5)
    for i := range(a) {
        a[i] = 5
    }
    fmt.Println(a)
}

func main() {
    a := []int{1} //  a := []int{1, 2}
    a = append(a, 0)
    surprise(a)
    fmt.Println(a)
}

Про который вроде как знают, но вердикт ланг тимы "прост не пишите так": https://github.com/golang/go/issues/9458

Не вижу, да
Вы ответили «не копируйте», я на это обращал внимание. Для меня «не копируйте» == «не пишите»
> Неопределённое поведение — свойство языков и библиотек в определённых ситуациях выдавать результат, зависящий от реализации компилятора (библиотеки, микросхемы) и случайных факторов

Здесь поведение вполне определено, есть одна явная реализация слайсов и функции append, которая работает вообще всегда одинаково и не зависит от случайных факторов. Зависит от неслучайных факторов, от аргументов (функция зависит от своих аргументов, звучит нормально). Поэтому давайте не делать вбросов насчёт UB в го :)
Но дизайн слайсов при этом действительно содержит опасные и неочевидные сразу места и я готов согласится, что можно было сделать его и как-то поочевиднее с точки зрения пользователя.

Размер овераллокации при авторесайзе слайса не определён. Именно в этом месте возникает полноценное UB.

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

Серьезно? Вы Issue открывали?


On the playground, appending to an empty slice initializes the capacity to 2. This means that this will print "[0] [0 1]", because a and b share a buffer under those slice expansion rules. However, on my machine (64-bit Windows, go 1.4) an empty slice grows to a capacity of 1, and thus this prints "[0] [3 1]".

Если это по-вашему "всегда одинаково" то я даж не знаю.

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

Слайсы, мапы и каналы можно параметризовывать типами. Встроенные дженерики, фактически. А вот свои делать нельзя. И делать для них итераторы тоже нельзя

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

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

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

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

Что не помешало накостылить те же мапы максимально мерзким способом.

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

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


Если кратко, то если делать дженерики в языке мономорфизмом, то есть генерировать на каждый набор параметров свою функцию, то вы сильно замедлите компиляцию (и ещё будут сложности с оптимизациями дженерик-кода) и раздуете бинарники.
А если делать дженерики, как в джаве, то есть делать одну дженерик-функцию, которая принимает абстрактные типы, то придётся принимать поинтеры, то есть, выделять память на хипе, то есть это ударит по перфомансу. И всё ещё будут проблемы с оптимизациями кода, потому что JITа в го как-то и нет :)

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


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

Добро пожаловать в нашу уютную ламповую секту!
100-мегабайтное приложение на C# было заменено 564 килобайтами кода написанного на rust. 200 мегабайт памяти которые потребляло приложение на шарпах теперь превратились в 1.2 метра RAM для программы на rust. (Заодно избавился от двух докер-контейнеров в которых это приложение работало).
Будь осторожен, это ощущение затягивает и вызывает зависимость! Еще неделю назад ты переписывал на Rust маленькое изолированное приложение, потом снижал счета на AWS, и вот уже организм требует переписать на него Linux kernel или Hadoop.

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

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

Помни – ты не можешь сделать весь мир эффективнее.

Но это не значит, что к этому не надо стремиться!


Tutum. Simul. Celerem.

А чем плох С, на котором написано ядро линукса?)

Тем, что писать на нём крупные проекты без внесения UB в код — задача, непосильная простым смертным.

Я правильно понимаю, что задача считывать бинарные данные по сети с порта и сохранять с возможностью потом вытаскивания в виде CVS по датам?
Т.е. это одна таблица в БД фиксированного формата.
При том что 250Мб дамп памяти БД и +50Мб в год.

И для этой задачи: БД postgres (!!!!), C#, Node.js, docker и т.д.

ОФИГЕТЬ! Я всякое видел но такое…

С учетом накладных расходов Postgres на хранение данных, не нужна тут даже БД. Практически все данные в памяти поместятся.
Народ похоже уже даже не знает fseek функций для файлов…

Тут программка на C++/Java максимум строк на 500-1000 для такой задачи. Про rust не скажу. По работе не надо его. А ради любопытства — других вещей хватает.

Просто эталон как не надо решать задачи (я про то ПО, что было заменено).

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

  • regexp? Позиционно зависимые и фиксированные длинны. Приборы обычно другие и не выдают) из TCP/IP сокета.
  • Обработка времени — это что? из строки получить Unix time (long int)?
  • «фаззи поиск»… Поиск (дата) в памяти в упорядоченном массиве long int.

Для этих мелких задач нужно обязательно Postgres ставить?

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

Вот тот лаборант так и поступил…
От результата я просто потащился.

Кстати, сплошь и рядом такое вижу, пусть и не в таких вырожденных формах.
Нужно сертификат x509 из PEM файла считать — добавляем Bouncy Castle
Нужно пару результатов select из таблицы вывести с простым маппингом имен полей в lowercase имена json (простейший микросервис) — а как же без hibernate то обойтись.

А уж как выглядит типичный package.json у JS…
«Зачем писать 2*2. Может где то уже есть библиотека на 200Мб где уже реализована функция mulTwoTwo»

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

как говорит старая народная мудрость — если у вас есть проблема и вы решаете ее regexp — у вас две проблемы. Даже в расте с его классными машинами состояний найдутся камни преткновения.


из строки получить Unix time (long int)?

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


«фаззи поиск»… Поиск (дата) в памяти в упорядоченном массиве long int.

опять же — неизвестно какие данные у автора в базе — известно что там есть даты и есть какие-то валютные данные. fuzzy-поиск едва ли там действительно нужен, разве что веб-морде Ахо-Корасика навернуть для фильтрации по полям. Опять же автор упоминает, что большая часть запросов были про фильтрацию по дате, но это не все запросы. То бишь как минимум надо изобретать систему query, для работы с ними. Не очень тривиально и требует немало тестов по-хорошему. У раста с юнит-тестированием все конечно очень неплохо, но кому хочется писать тесты?
Короче велосипедить СУБД даже для данных с известным форматом — едва ли хорошая идея, стоящая затрат. Ну и автор вроде не настолько хлебушек, чтобы творить дичь из ваших примеров.

Я правильно понимаю, что задача считывать бинарные данные по сети с порта и сохранять с возможностью потом вытаскивания в виде CVS по датам?
Т.е. это одна таблица в БД фиксированного формата.


И для этой задачи: fseek (!!!!), велосипедные индексы, самодельные проекции и т.д.


ОФИГЕТЬ! Я всякое видел но такое…


С учетом накладных расходов HTTP на передачу данных, не нужна тут даже память. Практически все данные можно стримить напрямую из БД.


Просто эталон как не надо решать задачи.

Т.е. вместо простейшей консольный программы на C++/rust/Java в максимум 500 строк, которая в одном режиме (аргументы командной строки) стартует как demon или Windows service и:
0. Слушает сокет.
1. Получает данные из сокета (бинарные).
2. добавляет эти бинарные данные (C++ структура) с long int текущей даты конец файла.

А отдельный запуск этой же программы (с аргументами, например: "--from", "--to", "--outcvs") — получение csv файла, которая читает структурированные данные из файла и формирует csv.

Да я такие консольные утилиты и демоны(*nix)|сервисы (Win) довольно регулярно писал и пишу под подобные задачи.

Необходимо зачем то прикручивать HTTP и БД? Где Вы там все это увидели?
В описанном корявом решении лаборанта? внутри хостового служебного обмена между двумя программами? Потому что лаборант не знал как сразу из C# в БД сохранить?
Преобразовывала их в JSON, который был доступен через POST API. С шарпами он был не очень дружен, посему сохранять в базу данных всё решил на Node.js, который эту программу на шарпах дёргал, получал JSON, делал немного логики и гнал в базу данных (postgres)

Да еще Postgress! Если брать что то то по максимуму…

И да… 90% времени занимаюсь сервисами с http + json и хостовыми частями web и БД.

Но нафига все это для решения этой конкретной задачи «на входе читаем бинарные данные из сокета храни их и выдавай в файл по from->to критерию.
подключается вот к этой системе, сгружает бинарные данные по сети с порта, конвертирует их в удобочитаемый формат и сохраняет всё в базу данных.

Уточнени… утилита не слушает сокет, а открывает и читает из него, перекладывая данные в файл. Те же яйца только вид в профиль.
Т.е. вместо простейшей консольный программы на C++/rust/Java в максимум 500 строк, которая в одном режиме (аргументы командной строки) стартует как demon или Windows service и:
0. Слушает сокет.
1. Получает данные из сокета (бинарные).
2. добавляет эти бинарные данные (C++ структура) с long int текущей даты конец файла.

А отдельный запуск этой же программы (с аргументами, например: "--from", "--to", "--outcvs") — получение csv файла, которая читает структурированные данные из файла и формирует csv.


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

Необходимо зачем то прикручивать HTTP и БД? Где Вы там все это увидели?

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

Но нафига все это для решения этой конкретной задачи «на входе читаем бинарные данные из сокета храни их и выдавай в файл по from->to критерию.


Да потому что это преждевременная оптимизация сервиса, про требования к которому вы не знаете ничего. Если его вызывают раз в секунду то какая разница, хттп там или нет, есть БД или нет? Кроме того, база может быть файловая/инмемори, все эти sqlite/tarantool и прочее.

А требования: сегодня сортируем по from/to, а затра понадобилось выгрузку от конкретного юзера Х. И пошли пилить самодельные индексы. А потом понадобилось выгружать не все поля, а часть. Пошли писать SELECT'ы. И так далее.

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

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

К слову, может для кого то это и странно, но сплошь и рядом всякие отчеты/реестры забираются в банках не через HTTP вызовы, а передаются через SFTP или email.

Решение на голом C++ для HTTP сервиса… Я не мазохист. Для этого другие инструменты.

Да потому что это преждевременная оптимизация сервиса, про требования к которому вы не знаете ничего

Преждевременная оптимизация — это БД, Web API и пр., когда не понятно нужно это будет или нет вообще.
А консольная утилита с таким функционалом — пишется часа за 3-4 включая отладку.
Поскольку делаю и то и другое, то могу оценить трудоемкость. И… самое противное объем технической документации на ПО. О чем все как то обычно забывают.
На систему с БД+WEB API ее больше.

А по ему опыту, всякие «наверное пригодится» выстреливают в 5-10% случаев. И то не совсем так, как предполагалось изначально.
Я ее понял так как и сформулировал. Слов о внешнем интерфейсе получения выборок из данных в моей формулировки постановки не звучало.

Всё бы хорошо, но поиска по полученным данным у нас не было.

Кажется, звучало.


Преждевременная оптимизация — это БД, Web API и пр., когда не понятно нужно это будет или нет вообще.

Это не преждевременная оптимизация, потому что во-первых БД как правило уже есть, и вопрос только в том чтобы табличку там нужную создать, во-вторых потому что подключение какого-нибудь sqlite производится одной командой в пакетнике и гарантированно быстрее того чтобы писать это руками. Что тут говорить, я последние полгода занимаюсь переписыванием сервисов где решили сделать "свою простую базу". А что, казалось бы, задача элементарная: хранить сущности, возвращать их по некоторым простым условиям, иногда обновлять кусками или целиком. Но нет, триллионы багов: что-то не обновляется по каким-то причинам, при обновлении ключевого поля вроде id запись исчезает (и недоступна ни по старому айди, ни по новому), и прочие приколы.

И для этой задачи: fseek (!!!!), велосипедные индексы, самодельные проекции и т.д.


тоже самое подумал при чтении )

если поиск по одному параметру то есть куча готовых key-value хранилищ, в том числе embedded типа (Mapdb, Sqlite, RocksDB и много других) которые отлажены и готовые. Если бы я пришел на уже написанное кем-то решение, я бы предпочел иметь дело со стандартными а не самописными инструментами.
Прям вижу, как чувак набил портфолио и свалил в стартап )
А похоже это ход такой. Был лаборантом, а стал со списком в резюме:
JSON, C#, JS, Node.js, postgres, Doker, docker-compose

Крутой список! Даже не мидл… такой список если правильно резюме оформить и на сеньера тянет…
А если предыдущее место «секретнное», то и можно так в резюме подать, что и на лида.

И возьмут же…

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

Тэг <сарказм> я просто не добавил… Думал и так понятно.

Уф, что-то, кажется, я зря в своём резюме только три с половиной технологии пишу, в которых более-менее уверен (Haskell, C++, Idris, Agda). Мог бы ещё с пару десятков ключевых слов добавить.

Конечно зря, оно так и работает. К сожалению или счастью

Очень содержательно, спасибо. Сам бодаюсь с ржавым 2-3 месяца, и соглашусь, борьба с областями видимости и владением далась нелегко. И документацию читал пару раз, если бы была эта статья раньше, возмжно, она бы ускорила мой прогресс)
Были схожие ощущения от книги. Да и от самого языка
Я обычно начинаю изучение новых языков с перелистывания документации и решения олимпиадных задачек. В расте же я со всей скорости врезался в непонимание ввода данных из консоли. Почему он такой сложный?

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


let mut in = InputHelper::new();
while in.read_line() {
  let n: usize = in.get();
  for _ in 0..n {
    let v: f32 = in.get();
    ...
  }
}

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

Полный дамп базы данных весил 250Мб. Прирост данных составлял 50 метров в год.
Еще и Постгресс надо было выкинуть при таких объемах.

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

Про установку .NET и ее размеры — ну компилируйте под версию, встроенную в Win.
Полный дамп базы данных весил 250Мб. Прирост данных составлял 50 метров в год. Чего там, тривиальная задача. Но всё это крутилось на выделенном сервере с 32 гигами оперативки и 24 процессорами. Сервер выл и жужжал потому что приходилось гнать 16 докер-контейнеров с кластером Elasticsearch, который постоянно рушился от перегрузки.

Тут что-то не сходится. База в 250 мегабайт, прирост в 50 мегабайт в год — и сервак на 32 Гб и 24 процессоров? Кластер ELK? Ради выборок по датам и экспорта в CSV?
Есть подозрение, что и будучи переписанным на шарпе профессиональным шарповиком оно бы жило значительно скромнее, нет? Не на расберри, конечно, но вполне рядовой виртуалке.

Есть подозрение, что это было resume driven development (

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

docs.rs/crate/rust_decimal/1.9.0/features

У библиотеки rust_decimal есть features для того, чтобы интегрироваться в том числе с основными библиотеками баз данных

Так что нужно было добавить что-то такое в Cargo.toml, и всё просто заработало:
rust_decimal = {version = "*", features = [«db-tokio-postgres»]}
tokio-postgres = "*"

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


Мне как-то "звездочка" в зависимостях на гипер подсосала версию 0.0.1 при актуальной на тот момент 0.12.35

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

Но так да, совет хороший. Оставлять такое надолго не стоит

Обычно когда я делаю зависимость я беру последнюю которую иде подсказывает и не думаю:


img

Ну или просто можно написать cargo add some_crate (https://github.com/killercup/cargo-edit) и тоже автоматически получить последнюю версию. Правда, в этом случае надо следить за тем, чтобы не получить альфу будущей мажорной ветки вместо стабильной версии.

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


img

карго-эдит хорошая штука, но менее удобная, вот и все. Зачем мне второй инструмент если у меня в карго.томле всё прекрасно работает?

А опишите свой опыт в создании REST API на расте. Например, как выглядит простейший CRUD, работа с JSON, с каким-нибудь S3, RabbitMQ, redis и проч.

Вот недавняя статейка про это, TLDR человек намучался с простейшим API и пришел к выводу, что на вебе ему (пока) не место: macwright.com/2021/01/15/rust.html

TLDR: берется актикс-веб, подключается к нему diesel/r2d2 (хотя если бы я делал с нуля я бы взял sqlx вместо этого), реббит-редис работается так же как и любой другой язык.


Из минусов — отсутствие диая (то есть хендлеры знают, что нужно таскать из хедеров, что из тела, что из кверистринги, такой вот сервислокатор) и отсутствие генерилки опенапи описания по хендлерам: мы руками правим swagger.json и отдаем как статику вместе со swaggerUI.


Что до статьи которая линкуется — хз, мне не показалось что сделать простейшее апи сложнее чем с какимнеибудь экспресс-жс:


img


Чуть больше конкретики


Rust has a fair number of web server frameworks, database connectors, and parsers. But building authentication? You have only very low-level parts. Where Node.js will give you passport and Rails has devise and Django gives you an auth model out of the box, in Rust you’re going to build this system by learning how to shuttle a shared vec into low-level crypto libraries. There are libraries trying to fix this, like libreauth, but they’re nascent and niche. Repeat for plenty of other web framework problem areas.

Видимо под "you’re going to build this system by learning how to shuttle a shared vec into low-level crypto libraries" имеется в виду вот этот код:


pub fn validate_and_enrich(
    &self,
    req: ServiceRequest,
    credentials: &BearerAuth,
) -> impl Future<Output = Result<ServiceRequest, actix_web::Error>> {
    let f = self.auth_client.get_user_info(credentials);
    async move {
        let user_model = f.await?;
        req.extensions_mut().insert(user_model);
        Ok(req)
    }
}

...

let auth = HttpAuthentication::bearer(move |req, c| auth.validate_and_enrich(req, &c));
self.service(resource(path).route(route).wrap(auth))

Ну да, я практически стал специалистом по криптографии когда это написал )


Ну и далее по тексту


Rust makes you think about dimensions of your code that matter tremendously for systems programming. It makes you think about how memory is shared or copied. It makes you think about real but unlikely corner cases and make sure that they’re handled. It helps you write code that’s incredibly efficient in every possible way.

These are all valid concerns. But for most web applications, they’re not the most important concerns. And buzzword-oriented thinking around them leads to some incorrect assumptions.

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

я бы взял sqlx вместо этого


PsyHaSTe подскажите, а у вас работает intelli sense в IDE/text editor с sqlx макросами (да и вообще с макросами)? Я пробовал пару раз sqlx (у самого IDEA c Rust плагином), но как только дописываю ключевое слово макроса на этом помощь IDE заканчивается.

С макросами работает, но через раз. Я обычно Владу Бескровному баги завожу, он их понемногу поправляет, последний раз жаловался на макросы дизеля. Но у sqlx скорее всего процедурные, а там вряд ли IDE может помочь.


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


Идеальным мне кажется подход EF с code first когда схема БД хранится в коде и в БД может накатываться при желании. Тогда сборке не надо смотреть куда-то, она самодостаточная, но при этом и гарантии типчиков мы сохраняем. Но увы, не все коту масленница.

Офигенная статья! Единственное что мой граммар-наци забодался ctrl-enter жать. ;-)
И хотя rust и не такой сложный как Си в управлении памятью и ссылками

Хм… всё как раз наоборот.

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

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

А за этим вот «правильно — неправильно» обычно прячется маркетинг, нет?

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


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

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

Продолжая аналогию — в Расте вы скорее всего вообще не сможете зайти в тубдиспансер. Хотя если вы очень опытный доктор и пять месяцев были на курсах повышения квалификации… То сможете иногда заходить )))
Но это все не про «секреты правильного управления», это как раз подтверждает мои слова — управлять памятью в Си просто, но тяжело, требует внимания.

Остается тогда заключать что самый простой (и, видимо, лучший?) язык это брейнфак, куда проще сишки: всего 6 операторов и никаких странных правил и УБ.


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

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

Хотя это уже спор из серии «а что такое достаточно сложная программа».

Можно и другую аналогию привести. В Boeing 737 MAX конструкторы положились на квалификацию экипажа, на то, что экипаж распознает нештатную ситуацию с датчиком угла атаки и примет нужные меры (как язык С полагается на квалификацию программистов). В Airbus сделали мажоритарное троирование датчиков, чтобы не полагаться на экипаж, но экипаж может отключить эту систему (как в managed языках и расте).


Результаты известны.

Ммм, нет, в Эрбасе просто есть статанализатор — и он обязателен.

И вообще пример с MCAS он как раз скорее про обратное, про сахар и «все под капотом сделано умными людьми» — есть все нужные подписки, память освобождается где нужно, думать не требуется. Ну вот и не подумали потом в продакшене…

В эрбасе под капотом сделали сложно, но надёжно. И если что не так, то по индикации всё видно.


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

Ненене, MCAS как раз про «вся сложность под капотом», плюс закон протекающих абстракций Спольск. «Все просто под капотом» — это Ту-154.

Короче аналогии нас куда-то завели…

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

При чем тут аварийность? 0_о

Мда, короче и тут аналогии увели нас совсем в другую сторону. Зря я это начал…
Так именно в эту же: если инструмент «тонкий», то пользоваться им сложно (или тяжело, что с точки зрения пользователя почти одно и то же).
Нужны абстракции правильной толщины — если абстракция слишком бедная, то ей пользоваться сложно (не с точки зрения предсказания, какой получится результат выполнения этой одной строчки, а с точки зрения того, как эта одна строчка влияет на всю программу), потому что она позволяет слишком большим количеством способов отстрелить себе ногу.
А если слишком богатая, то оказывается сложно предсказать поведение системы «под капотом» (например, в случае управления памятью это будет сборщик мусора, который добавляет новые задержки, над которыми нет контроля).
Да, и поэтому связка С+C# в принципе решает все задачи, стоящие перед человечеством.
Зачем C и C#?

Чистый ассемблер уже является тьюринг-полным.
GC нет, так что богатая абстракция не покрыта.

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

Ну, да, я говорил скорее про императивные языки.
Да, и поэтому связка С+C# в принципе решает все задачи, стоящие перед человечеством.

И как же эта связка решает задачу обработки ошибок?

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

Нет, я про ошибки в принципе. Навроде "Файл не удалось прочитать" или "Число не удалось распарсить"

Эм… Ну, так же, как и в других языках… или я не так вас понял?

Монады в C# выглядят как-то не очень.

В любых. Отсутствие файла — проблема для любого языка, логично?

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

Семантика действий будет как-то отличаться? Или просто «исключения фу, нужны коды ошибок»? В чем суть диалога этого?

В том, что раст дает безопасность "функция всегда вернет результат и ошибки не будет" на уровне сигнатуры, а джава — нет. В джаве вы функцию int Foo(int x) => x*x можете переделать в


int Foo(int x) { 
  if (x == 0) throw new SomethingException();
  return x*x:
}

А в расте подобное изменение — ломающее и приведет к ошибке компиляции (т.е. невидимый эксепшн в продакшн не пролезет таким образом). Ну и так далее.

Ииии… И что?

Я специально выше написал — ну ок, будут у вас вместо исключений коды ошибок. Какая разница? Если для вас важно, что Х не может быть нулевым, то вы либо бросаете исключение, либо возвращаете nullable-тип, возьмем эти два варианта. еще раз — если для вас это важно, то вы сообщаете это из функции и обрабатываете это. Если неважно — не обрабатываете.

Почему мы это обсуждаем?

Такая что добавление необрабатываемого кода ошибка приводит к ошибке компиляции. А добавление нового вида эксепшнов — к "упс" на проде

То есть вы акцентируете внимание на том, что в одном случае вы _обязаны_ обработать все коды ошибок на уровне правил написания, а в другом случае вы эти правила можете обойти? Ну так это вопрос code of conduct и статанализаторов. Ну и вас же, очевидно, не надо учить тому, как обработать все коды ошибок, но сделать это так, чтобы было бесконечно больно? :)

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

Я повторю свой вопрос, возможно после этого обсуждать будет нечего — мы вот реально от обсуждения управления памятью пришли сюда, к обсуждению исключений, чтобы написать, что исключения хуже, чем коды ошибок функций?
Ну так это вопрос code of conduct и статанализаторов.

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

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

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

Главное — что вы его напишете и не забудете. А явную лажу (вроде unwrap без обработки) будет ещё лучше видно на кодревью (которые не стоит выкидывать в любом языке).

Ровно, ровно наоборот! Написав какую-то лажу вы как раз таки спрячете проблему, она не проявится даже в рантайме. К сожалению очень много таких проблем я видел…

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

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

Так система типов — это как раз ваш статанализатор.


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

Ну если в правилах есть, что не должно быть повторяющегося кода, то СА решит вопрос, а система типов?

Мы же вроде обсуждали написание лажи и игнорирование ошибок.

Что будет видно анализатором, да.

А чтобы увидеть ошибки, которые проигнорированы (намеренно, например) анализатором, мы сделаем анализатор анализатора!

Это как? Я не понял. У нас плохой анализатор?

Даже если идеальный (чего не бывает), то, может, там написано


namespace ConversionUtils
{
  std::optional toInt(const std::string&);
}

int readInt(std::string path)
{
  /// mySuperDuperAnalyzer:ignoreExceptions
  File file { std::move(path) };
  /// mySuperDuperAnalyzer:ignoreExceptions
  auto str = file.readAll();
  if (str.empty())
  {
    // mySuperDuperAnalyzer:ignoreUsingMovedFromObject
    Logger() << "can't read " << path;
    return -1;
  }
  /// mySuperDuperAnalyzer:ignoreUncheckedOptionalValue
  return ConversionUtils::toInt(str).value();
}

Так обязательно должен быть комментарий // abusing knowledge that X does Y when Z, и чуть ниже коммит который вставляет доп проверку с тегом #[fixed(since = "2021-05-05", reason = "add additional checks for situation when Y doesn't do Z")]

Ну это уж чересчур )))

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

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

Только если с точки зрения её восприятия человеком, а это вопрос скорее социальный, чем технический. Корректные и выразительные системы обеспечения правильности известны лет 40-50, языки с ними существуют лет 30, так что технически концепция как раз вполне реализуема.

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

CompCert, seL4.


И, кстати, в статье от авторов CSmith про CompCert отдельно сказано:


The striking thing about our CompCert results is that the middle-end bugs we found in all other compilers are absent. As of early 2011, the under-development version of CompCert is the only compiler we have tested for which Csmith cannot find wrong-code errors. This is not for lack of trying: we have devoted about six CPU-years to the task. The apparent unbreakability of CompCert supports a strong argument that developing compiler optimizations within a proof framework, where safety checks are explicit and machine-checked, has tangible benefits for compiler users.

Извините, я не понял, к чему вы привели эти ссылки. Они как раз подтверждают _мои_ слова.
Надо-же! Кто-то про seL4 микроядрышко вспомнил…

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

Вот, да, контракты для блокчейна. И все равно это редкая редкость. И все равно непонятно, при чем тут Раст.

Он — ещё одна точка. Даёт меньше гарантий, чем та наркомания, но писать проще.

Вопрос в том, как измерять сложность.
Каждая отдельная команда управление памятью — проще, конечно, в С.
Но для не-тривиальной программы на С, следить (руками) за всеми инвариантами (чтобы не получить use after free, double free, etc) уже сложнее (хотя каждый отдельный примитив по-прежнему проще).
Я вас поправлю, извините — _тяжелее_, а не _сложнее_.

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

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

А чем, собственно говоря, платить?

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

Раст за счет совершенно другой концепции обеспечивает еще и скорость, но сложность возрастает критически…
Так бейзлайн не Джава, а С.

Тезис примерно такой: Написать большое приложение на С так, чтобы там не было проблем с памятью сложнее, чем написать приложение на Расте, чтобы там не было проблем с памятью.
Несмотря на то, что написать однострочник на С, конечно, проще.
Вот именно этот тезис кажется надуманным. Просто для сравнения нужны примеры, и у Си они есть, а у Раста мало что вообще есть. Где-то там есть, у социал-демократов, но не тут. И причина же ясна — сложность высокая у языка.
Опять ссылки, непонятно что доказывающие. Вот мы знаем например, на чем написана ОС Integrity, примем это за некий уважаемый пример. И она в принципе довольно сложна, да. И как-то получилось, угу. Теперь давайте посмотрим на симметричный ответ на Расте и попытаемся понять, насколько использование Раста в этом примере сэкономило авторам какие-то ресурсы, или, возьмем выше, сделало реализацию проекта вообще возможной.
Проблема такая:
И она в принципе довольно сложна, да. И как-то получилось, угу.

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

В то, что на С написать программу, которая иногда работает проще, чем на расте написать программу, которая иногда работает — никто не спорит.
Ну, да, поскольку симметричного ответа нет — нечего и обсуждать, я вас понял))

Симмеетричный — это написаная ОС? Пожалуйста, есть redox.


Что до утверждений "да вы посмотрите на си вон линукс есть и Integrity" — если на протяжении 40 лет вливать миллионы (миллиарды?) долларов и тысячи человеколет в проект то можно и на брейнфаке его завести. Качество языка это показывает никк, кроме того, что когда их писали 40 лет назад из альтернатив был паскаль и лисп. Только эти годы индустрия не спала и что-то придумывала. Аргумент "деды писали и нам велели" такой себе

Не, осей уровня Редокс — и на Си тележка. Предполагался симметричный пример.

Из вашего второго абзаца очевидно следует, что на Расте такие же операционные системы будут написаны за 4 года усилиями пяти человек? Что же, думаю скоро мы это увидим.

За 40 лет, да. Давайте, 21.01.2061 откопаю тред и напишу что вышло.

Эмммм, а зачем нам тогда Раст, если надо снова сорок лет? Вы же говорили, что на нем…

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

В Java есть ConcurrentModificationException, которое в Rust просто не требуется.

Я не понял, наличие данного эксепшна — это проблема? И как это связано с управлением памятью например?

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

Извините, это «за все хорошее против всего плохого». Очевидно, что лучше в компилтайме.

Я просто не пойму, куда клонит комментатор выше. Разговор выгляди так:
1) Ручное управление памятью — фу.
2) Берите джаву, там не ручное.
3) В джаве эксепшоны.
4) Ииии… чо?

Эксепшоны — механизм _для всего_, не только для решения проблем с памятью, но и с ошибками арифметики, выходом за границы (есть много где, и везде не бесплатно), таймаутами и черт знает чем. Когда у вас в рантайме из-за SET флаг carry установится, а потом ответный пакет не придет, вы на компилтайм как молиться будете — сидя или стоя? или может нам понадобятся эксепшоны/прерывания/коды_ошибок?
Ииии… чо?

А в расте они не нужны для этого. Раст даёт больше статических гарантий.


Эксепшоны — механизм для всего

Но это не значит, что их надо использовать для всего (или вообще что надо использовать).


выходом за границы (есть много где, и везде не бесплатно)

В упомянутых рядом языках можно сделать бесплатно настолько, насколько это возможно (то есть, валидировать только пользовательский ввод, скажем, и математически минимально необходимое число раз — скажем, если вы в рантайме проверили, что i < len(arr), и вы знаете, что arr имеет больше одного элемента, то в рантайме проверять, что i / 2 + 1 < len(arr) уже не нужно).


Когда у вас в рантайме из-за SET флаг carry установится, а потом ответный пакет не придет, вы на компилтайм как молиться будете — сидя или стоя?

Я не понял, как связаны флаги и ответный пакет.

Но это не значит, что их надо использовать для всего (или вообще что надо использовать).


Еще раз — положим, что возможна какая-то ошибка. Они бывают разные. Так или иначе их надо обрабатывать. Исключения — один из механизмов. С его помощью можно построить логичную систему обработки любых ошибок, в том числе ошибки, упомянутой выше. ЗАЧЕМ вообще мы все это здесь обсуждаем? Зачем это было упомянуто при обсуждении управления памятью? Где логика? Просто чтобы лишний раз пнуть условную Джаву и сказать, что Раст хороший?

Я не понял, как связаны флаги и ответный пакет.


Никак. Это примеры двух разных ошибок в рантайме, которые надо как-то обрабатывать. Раз уж мы зачем-то (повторяю — не по моей инициативе) начали обсуждать исключения и то, что статические гарантии конечно гораздо лучше.
Еще раз — положим, что возможна какая-то ошибка. Они бывают разные. Так или иначе их надо обрабатывать. Исключения — один из механизмов. С его помощью можно построить логичную систему обработки любых ошибок, в том числе ошибки, упомянутой выше. ЗАЧЕМ вообще мы все это здесь обсуждаем? Зачем это было упомянуто при обсуждении управления памятью? Где логика? Просто чтобы лишний раз пнуть условную Джаву и сказать, что Раст хороший?

С тем, что джава по-умолчанию делает "Необрабатываемые исключения" которые нужно явно обработать трай кетчем, а раст — не разрешает так делать и требует обработать все кейсы явно. Потому что в расте ошибки можно нормально комбинировать. Ошибка из библиотеки А и библиотеки Б при попытке их скомбинировать становится ошибкой A | B. В джаве всё один Exception.


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

Вы когда-нибудь обрабатывали ошибки как-то по особому, кроме как «вывести в лог и жить дальше»?
let application_url = config
        .application_url
        .as_ref()
        .map(|x| x.as_str())
        .unwrap_or("0.0.0.0:8800")

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

А где тут предполагается должно быть выброшено исключение?

При чтении application_url. Эквивалентный сишарп код (из другого проекта):


public sealed class BufferingSettings : IBufferingSettings
{
  private readonly IConfiguration configuration;

  public BufferingSettings(IConfiguration configuration)
  {
    this.configuration = configuration;
  }

  public int BufferSize => 
    configuration.GetValue<int>("BUFFER_SIZE");

  public TimeSpan BufferTimeout => 
    configuration.GetValue<TimeSpan>("BUFFER_TIMEOUT");
}
Все равно нифига не понял, что не так. Можно написать чтото типа
let application_url = config
.application_url
.as_ref().as_str_or(«0»);
или хотябы
try {
url =config
.application_url
.as_ref().as_str();
} catch {
url = «0»;
}

В чем разница? Где преимущество? (Да, может строк побольше/поменьше, ок)

Разница в том, что раньше application_url возвращал просто строку (потому что конфиг был в памяти и значение всегда было). А потом конфиг перенесли в файл, появилась возможность ошибки, и в этот момент компилятор мне подчеркнул все места где я его использовал и сказал "ты тут ошибки не обрабатываешь", и я вставил дефолтный кейс с 0.0.0.0:8080. Разница в том, что я узнал про все места которые нужно поправить от компилятора и до того как собрал приложение, а не в продакшне.

1) Но сейчас вы например также не видите все типы ошибки, которые могут к вам прилететь. Я вот например смотрю, и вообще не понимаю, какой тип ошибки там может быть? Ошибка перевода в as_str()? Но она была и раньше…
2) Что значит application_url возвращал? Мне кажется, application_url это какой-то объект, что он может возвращать? Возвращает функция, а единственная функция что я вижу это as_str()
3) Что такое «появилось возможность ошибки»?

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

Парсинг (от конфигов до пришедшей по сети ерунды), запуск внешних программ и обработка результатов от них, смена fast path на slow path, хитрые системы из последовательных попыток сделать действие ещё раз, но не более N раз и с увеличивающимся интервалом, откат соответствующих ошибке действий или состояния…


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

Да, StopIteration exception это просто какая-то насмешка над принципом "используйте исключения только для важных исключительных ситуаций"

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

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

"Надо делать так, как нужно, а так, как не нужно, делать не нужно".


Хорошие слова, жаль только, что совершенно неконструктивны.

Да, но я к сожалению пока не видел, как сделать по-другому
И в джаве все не один эксепшен. Вы также можете написать
try {
} catch (MyException1 e) {
} catch (MyException2 e){
}

и даже так
try {
} catch (MyException1 | MyException2 e) {
}

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


Неплохая статья на тему: http://www.lighterra.com/papers/exceptionsharmful/

Потомучто в exception-ы нужно засовывать действительно исключительные ситуации. А нормальное выполнение работы должно идти возвращаемым значением.
В функциональщине тоже самое, нет одного типа Result, равный LineNumber | Error1 | Error2, есть класс Either, который является структурой LineNumber | (Error1 | Error2), что явно подчеркивает разницу между нормальным ходом выполнения программы и исключительным
А дальше, при попытках обработать эти Error-ы начинаются разные неприятные траблы типа конвертации одного Error-а в другой, добавления нового Error-а и т.п…
Потомучто в exception-ы нужно засовывать действительно исключительные ситуации.

Все это говорят, но никто так не делает. Отсутствие ключа в словаре — исключение. Не получилось попарсить строку в число — исключение. Файл не найден — исключение. Сервер вернул 404 код — исключение (хотя может я бы предпочел получить какое-то дефолтно-пустое значение? Не, не нужно).


Мантра хорошая, в реальном мире только не работает.

… Взять head от пустого списка в haskell-е — исключение… Ой. Так может быть дело не в языке и не в сум-типах?
Как по-вашему это должно работать?
Так что все-таки нужно программистам, чтобы писать код без исключений? Вот им дали язык, дали сум-типы, и эти самые программисты в самой первой функции, которая встречается начинающему программисту (на этом языке) выкинули исключение

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


Просто берете и пишете, это несложно.

Что мешает это делать в любом другом языке?

В каком любом? В хаскелле так и работает во всех приличных либах (да, прелюд говно и неприличный). В скале в этих ваших котоэффектах так же.


В остальных мешает:


  1. то что стандартная библиотека не следует этому. Писать врапперы всех используемых стандартных и просто популярных библиотек — титанический труд, и как правило оно того не стоит.
  2. то что в языке нет обычно удобных средств для комбинирования ошибок, взвратов и т.п., тот же Try трейт из раста. Без монадической записи работать с ошибками становится портяной из Match(...), разработчики знакомые с LanguageExt передают привет.
Ну, стандартная библиотека, не стандартная… Я почему-то считал что прелюд и является той самой стандартной библиотекой.
А другие библиотеки ведь тоже пишут люди. Кто-то же написал нестандартную библиотеку под хаскел? Что мешает сделать тоже под другие языки?

Не путайте стандартную библиотеку и прелюд. Хаскель тоже вполне себе ок, просто пропишите:


{-# LANGUAGE NoImplicitPrelude #-}

И не будет больше у вас ногострельного head

Вот им дали язык, дали сум-типы, и эти самые программисты в самой первой функции, которая встречается начинающему программисту (на этом языке) выкинули исключение

Функции head примерно столько же лет, сколько хаскелю (то есть, лет 30), и это одно из неудачных решений, которое тянется по соображениям совместимости (да, таковые соображения есть даже в хаскеле).

Ну тем не менее и в расте, который вроде бы свежевыпеченный язык, избавленный от разного легаси, при обращении a[10] возникает исключение

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

И что теперь делать? Не писать на нем?

Зависит от ваших целей. Я вот не пишу.

Ну тоесть мы сошлись на мнении, что раст ужасный язык? (Как и haskell)

Да. Более того, на самом деле все языки ужасные (у агды вот очень кривые эвристики для higher-order unification). Просто они по-разному ужасные. Условный раст у меня вызывает даже некий интерес, а на джаве или, тем более, питоне писать даже за деньги не хочется.

Ну я примерно к этому и вел

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

Что мешает писать a.get(10) в любом другом ЯП?

В любом другом, где есть проверяемый во время компиляции Option/Maybe/etc. — ничего не мешает.

Ну вроде боле-менее везде есть. В CPP вот есть. В Java, C# вроде тянули, не знаю, давно не интересовался

Отсутствие нормального типа для возможности выразить опциональное значение

Чем optional не угодил?

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

Ну вот даже в двух из «самых серьезных языков» ее далеко не везде используют. А вы хотите прям во всех языках это сделать обязательным

Её не везде используют потому что сначала сделали "как в си", а когда решили подумать головой оказалось что 3 billion devices are running Java. Щто поделать, исторический процесс.

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

Раст выигрывает в надежности, тут и спорить не о чем. Забыть обработать ошибку в расте нужно


1) постараться
2) это можно нагрепать/запретить линтерами/...


В жабошарпах подобного нет.

Конкретно в Java — тем, что он nullable ¯\_(ツ)_/¯
И при это Optional.of не переводит автоматом null в отсутствующее значение, нужно более громоздкое Optional.ofNullable

std::optional из C++17 как раз хорошая штука, сам использую. Но до выразительности Optional из Rust не дотягивает, потому что:


  • единственное, что мы можем — это делать проверку
  • всяких map, bind/then, or_default итп там нет
  • паттерн-матчинга нет
  • "?" тоже нет (хотя это и костыль, вызванный отсутствием HKT, монад и do-нотации)

В итоге, повсеместный std::optional превращает код в лапшу сильнее, чем в Rust.

Не Exception, а необрабатываемый panic. Выразить безопасную индексацию без использования зависимых типов, вроде как нельзя.


С одной стороны, необрабатываемый panic хуже, чем эксепшн, который можно перехватить. С другой стороны, это гарантирует, что никто не придет, и не начнем вместо идиоматичных Option/Result использовать "эксепшны" там, где они не нужны.

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

В узких случаях можно:


use std::ops::Index;

enum Idx { I0 = 0, I1, I3, I4 }

impl<T> Index<Idx> for [T; 4] {
    type Output = T;
    fn index(&self, idx: Idx) -> &Self::Output {
        &(self as &[T])[idx as usize]
    }
}
Выразить безопасную индексацию без использования зависимых типов, вроде как нельзя.

Помнится, было у нас тут одно извращение на эту тему...

есть класс Either, который является структурой LineNumber | (Error1 | Error2), что явно подчеркивает разницу между нормальным ходом выполнения программы и исключительным

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


Более того, после нескольких итераций наворачивания абстракций можно писать функции с типом вроде


foo :: (MonadError e m, SupportsError HostNotFound e, SupportsError BadSSL e, …) => Host -> Port -> m Connection

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

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

Я выше только что ответил на вопрос про эксепшны в джаве, смысла писать, предлагаю в 1 месте все же это обсуждать.

Верно, у меня условный readFile вернёт Either FileError Contents.

А, отлично, сделайте то же самое в блоке catch().

С радостью, только я про него забыть могу, а тут компилятор не даст.

Но забить можете все равно. И в этом проблема — если вы не готовы сразу сделать все хорошо…

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


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

Помню как нужно было добавить один вариант в std::variant в C++. Причем весь код у меня был написан через visit, без holds_alternative, то есть везде было бы заметно, что я что-то не обработал. Добавление этого варианта заняло несколько часов, и в какой-то момент я с удивлением обнаружил, что в половине мест, где меня предупредил компилятор, у меня по-прежднему лежат заглушки (ведь нужно же было как-то скомпилировать программу дальше, чтобы найти следущее место, куда нужно еще добавить)

Ну так заглушки можно взять и прогрепать.


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

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

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


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

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

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


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

Так надо было просто прогрепать же.

> К сожалению, иногда приходится работать и с чужим кодом.
На любом языке придется работать с чужим кодом. Если конечно, этот язык достаточно популярен, чтобы встретить другого такого человека… ах вы хитрец… :)

> Так надо было просто прогрепать же.
Дык откуда я знал, что нужно прогрепать? Я несколько часов вносил разный код. И понадеявшись на «мощную систему типов std::variant-а» о собственно этом варианте подумал в одну из самых последних очередей
На любом языке придется работать с чужим кодом.

Да, поэтому чем больше гарантий даёт язык, а не всякие там coding style guidelines и кодревью, тем лучше.


Дык откуда я знал, что нужно прогрепать? Я несколько часов вносил разный код. И понадеявшись на «мощную систему типов std::variant-а» о собственно этом варианте подумал в одну из самых последних очередей

Ну так пишите [](NewlyAddedType) { /* TODO */ } хотя бы и грепайте потом по TODO. Иначе это уже какое-то совершенно непонятное мне ССЗБство.

Да, поэтому чем больше гарантий даёт язык, а не всякие там coding style guidelines и кодревью, тем лучше.

Любой язык можно превратить в javascript при должном желании

Ну так пишите [](NewlyAddedType) { /* TODO */ } хотя бы и грепайте потом по TODO

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

Так и C++ не относится к развитым типам.


В этом моём хаскеле я бы написал undefined, и потом прогрепал бы по undefined.
В этой моей агде я бы написал {! !}, а тайпчекер бы не дал импортировать этот модуль из другого модуля, пока я не исправлю эту дырку на нормальный код.

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

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


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

Да, дак я и говорю — сломать можно что угодно. Смысл говорить о каких-то гарантиях, если все ломается 100%. Все равно весь процесс верификации кода лежит на программисте
Все равно весь процесс верификации кода лежит на программисте

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

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

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

Какой стиль мне нужно установить и где, чтобы проверять только маленькую часть кода на плюсах? И какую, кстати?


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

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

Какой стиль мне нужно установить и где, чтобы проверять только маленькую часть кода на плюсах? И какую, кстати?

Зависит от задачи. Если хочется вообще упороться по тойже безопасности с памятью, то можно только shared/week_ptr-ы использовать исключительно везде. Если же хочется более гибкой производительности, то будет сложнее. Тот же CppCoreGuidline можно попробовать например.

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

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

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

Но ведь они небезопасны.

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

А какого класса ошибки хочется переключать?
Возможность не вернуть ничего, когда по сигнатуре функции нужно вернуть? Это todo! или unimplemented! в расте поддерживает.
Вызвать функцию, которой нет у типа, который вызывается? Кажется, такое не нужно выключать — это гарантированно упадёт в рантайме.

Больше про второе. Да, оно гарантированно упадёт в рантайме, если когда-нибудь вызовется. Но могут быть нетехнические гарантии, что или не вызовется (в UI вообще нет), или что падение не будет считаться багом (нефиг годовой отчёт в мае составлять).

Ну вот есть todo — упадет в рантайме.

Можно какой-нибудь конкретный пример?

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

В хаскеле есть такое.


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

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

Заглушка unimplemented!() хорошо помогает найти именно ту заглушку, которая вызвала проблему.
А эта заглушка падает на этапе компиляции? Если да, то это не сильно бы помогло, так как я как раз пытался в первую очередь скомпилировать программу
Нет, она специально сделана так, чтобы было можно скомпилировать незаконченный код.
Она возвращает ровно тот тип, который от неё ожидают. А в рантайме падает, если выполняется.
Выглядит примерно так: play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=a5fb23799622af07da51280faa873479

В идрисе кстати сделано так, что и падает, и ещё и ожидаемый тип подсказывает (А не просто боттом). Оч круто. В расте увы, решается разве что тулингом который при попытке закоммитить такйо код сделает ворнинг что в коде найдены тудушки и неимплементированные куски

Типы, к сожалению, в расте не всегда можно вывести: всякий Box в сигнатуре функции не позволяет сказать какой именно тип ожидается на месте этого unimplemented!..

Из забавных вещей — с 1.40 появился макрос todo, который делает то же самое: doc.rust-lang.org/std/macro.todo.html

Да может он все вывести, попробуйте вместо unimplemented!() сунуть какой-нибудь () он мигом скажет, какой тип вывел. Не обязательно конкретный прям тип, но он может хотя бы намекнуть что ожидается Arc<Vec<{unknown}>>


Туду вроде ввели просто чтобы писанины меньше было. Хз, пусть будет, не мешает вроде.

Я разделяю по смыслу: todo для "ещё недоделано", а unimplemented для "никогда не будет вызваться". Бывают такие случаи, что реализация трейта используется только через интерфейс, который гарантирует отсутствие вызовов некоторых методов трейта.

Ну это не сильно отличается от exception видимо

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

Не понимаю здесь претензию. Как я понял, undefined это типа exception, может чуть получше
Ключевое отличие от exception'а — его нельзя поймать (а значит восстановить вычисление после). Это способ поменять поток выполнения, но ровно одним простым способом — прервать его (в Питоне или Джаве, например, exception — это способ управления потоком — его можно поймать где-то выше по стеку).
Ну обычно вводятся разные политики для ловли исключений. Какие-то ловятся, какие-то нет. В Яве не рекомендуется ловить Error-based исключения (или наоборот, Exception-based исключния? Не помню...), потому что предполагается, что если вылетело Error-исключение, то сделать уже ничего нельзя. Так что можно приспособить это undefined под Error-исключение например.

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

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

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


По коду на питоне (или джаве) не понятно мы специально пробросили этот эксепшен выше (где он успешно залогировался), а не обработали на этом уровне (где можно было что-то исправить — вернуть дефолтное значение, например). Раст в этом месте более эксплицитный — паники нельзя поймать ни на каком уровне, а исправимые ошибки записываются в типы (и есть три варианта как их обрабатывать — явно передать их выше, обработать на своём уровне или запаниковать=пробросить выше неисправимую ошибку). Чем-то похоже на Checked Exception в Джаве, только работает.

Та же история с todo!(). Написать так проще, чем писать throw NotImplemented (буков меньше).
Раст в этом месте более эксплицитный — паники нельзя поймать ни на каком уровне

Эм, можно же, если паника реализована через раскрутку стека.

Вот именно что "если реализована". А как она будет реализована — решается при сборке программы, то есть, к примеру, автор библиотеки в принципе не может рассчитывать на panic=unwind. Наглядный пример — https://github.com/rust-lang/rust/pull/71400.

автор библиотеки в принципе не может рассчитывать на panic=unwind.

Рассчитывать не должен, но бывает по-разному. Скажем, субстратовский (блокчейн) рантайм требует panic = "unwind".

Скажем, субстратовский (блокчейн) рантайм требует panic = "unwind".

Спасибо, буду знать, чем пользоваться не стоит.

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

Вопрос удобства написания кода.

Я б сказал удобства чтения, понимания прежде всего. Вон вы пишите "По коду на питоне (или джаве) не понятно..." Написать было удобно, а понять — нет

Unimplemented чуть получше эксепшна, да. Но кроме него есть и другие ошибки: сеть не нашлась, файл не открылся и т.п. Я бы предпочел узнать о них во время компиляции, а не в рантайме.


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

Вопрос в том, может ли возникнуть исключение в строчке Foo foo = getFoo()? В расте я могу однозначно ответить на этот вопрос:


let foo = get_foo(); // не может
let foo2 = get_foo()?; // может и будет прокинуто выше
let foo3 = get_foo().unwrap(); // может и не прокидываем. Если вдруг ошибка реально произошла то будет паника и приложение завершится (по сути UnhandledException). По этому можно грепать или даже настроить линтер чтобы он запрещал необрабатывать ошибки.
let foo32 = foo3(); // забыли проверить результат, компилятор выдаст ворнинг (который можно сделать ошибкой)

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

let foo32 = foo3(); // забыли проверить результат, компилятор выдаст ворнинг (который можно сделать ошибкой)

Только если foo32 нигде дальше не используется.
Если используется — у него будет не ожидаемый тип
(и let foo32: u32 = foo3() просто не скомпилируется).

Ну это понятно, вопрос про то когда у нас операция типа write(...), и возвращаемое значение Result<(), Err>, что бывает очень даже часто.


Выше опечатка. должно быть let foo32 = get_foo2();

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

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

Покажите любой код на расте где начинается ад, будет что обсудить предметно :shrug:

Ну, во-первых, мне нужен хотябы список opensource проектов, чтобы я хотябы знал куда смотреть.
Во-вторых, нужно сойтись на том, что считать адом. Точнее, что считать НЕадом. Я правильно понимаю, что НЕадом вы считаете использование исключительно возвращаемых значений, без panic, без assert, без unwrap?

ну serde, diesel,… — любой популярный подойдет.


Я правильно понимаю, что НЕадом вы считаете использование исключительно возвращаемых значений, без panic, без assert, без unwrap?

Совершенно верно, в библиотечном коде их быть не должно, за исключением тестов и иногда редких кейсов, например анврап env переменной наличие которой гарантируется cargo'м или код а-ля
if arr.len() > 0 { let first = arr.get(0).unwrap()) }

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

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

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

actix-web, rocket, warp?


даже в этих библиотеках есть и паники, и unwrap-ы

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

Ну, actix-web я смотрел, там паники на каждом шагу
А даже если из представленных выше библиотек смотреть, то например вот

github.com/serde-rs/serde/blob/2b504099e4e480625e74d3599f5ddab2f42c63de/serde/src/de/utf8.rs#L44

github.com/serde-rs/serde/blob/2b504099e4e480625e74d3599f5ddab2f42c63de/serde_derive/src/bound.rs#L305

github.com/diesel-rs/diesel/blob/a4b8031f6a096c8ea4032e7d8f05711d7f0dea1a/diesel_cli/src/database.rs#L41

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


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


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


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

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

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

Первый пример: создать структуру возможно только методом, расположенным в том же файле, все возможные пути исполнения гарантируют, что в указанной строке ошибки не будет. Теоретически, здесь можно было бы использовать unsafe { str::from_utf8_unchecked(&self.buf[self.pos..]) }, но в случае корректной реализации модуля паника будет выкинута оптимизатором, а в случае ошибки unchecked-версия будет содержать UB.


Второй пример — да, в данном случае это "срезание углов", но условно-допустимое, т.к. паника находится в процедурном макросе и приводит не к завершению программы, а к ошибке компиляции. Формально более корректным в этом случае было бы выводить вызов compile_error!, но протаскивать Result через весь код только для того, чтобы в одном редком случае получить чуть более удачное сообщение об ошибке — в данном случае это, как видно, оказалось не стоящим усилий.


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

actix-web считается достаточно небрежно написанным по стандартам раста, с ним было связанно несколько набегов коммьюнити в реддит, возможно слышали. Что до примеров в серде — они все как раз и являются if arr.len() > 0 { let first = arr.get(0).unwrap()) }, где выше уже сделали проверку (например, инвариант конструктора), и делать её же второй раз уже не нужно. Если же такой инвариант нарушился — то как раз логично упасть потому что это критический баг. Юзеркод тут исправить ничего не может и не должен.


В первом случае мы уже знаем что строка — валидный UTF, парсить второй раз мы не хотим.
Во втором случае паника происходит в процедурном макросе (вы же обратили внимание?), во время компиляции, не в рантайме. Просто при попытке собрать код вам компилятор выдаст среди ошибок строку "Serde does not support const generics yet".


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




В одной картинке


img

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

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