Комментарии 315
Если Вам платят за рекламу раста, то мало. Если не платят, то должны начать:)
Вопрос: на фрилансе 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? Или это на порядки больше времени?
Веб на хаскеле? Так действительно делают?
Делают. На прошлый проект к нам пришел дев, ушедший с проекта, где веб на Хаскале делали.
Ну хаскель-код для меня как китайский текст почти ( А экосистема и есть приближенность к жизни.
Просто потому что вы даже не пробовали по-настоящему.
Складывать из существующих кубиков (ругаясь сквозь зубы на монадические комбинаторы), наверно получится у всех. А вот в том, что все смогут полностью использовать возможности этого языка, я сильно сомневаюсь. У меня не получилось.
Вопрос практики. Побороть лайфтаймы у меня заняло год неспешного разбора с языком (в продакшне это было бы 1-2 месяца думаю), у хаскелля все то же самое. Учиться пользоваться незнакомыми концепциями всегда сложно. На первом курсе универа у меня год заняло разобраться в том что такое циклы и массивы)
На первом курсе универа у меня год заняло разобраться в том что такое циклы и массивы)
Поведу себя грубо и скажу, что это _преувеличение_ для красного словца.
Нет, совершенно не преувеличение. От необходимости обойти матрицу MxN и посчитать что-то (например среднее для каждого столбца) у меня наступала дикая паника и я не мог этого сделать. И только ближе к июньскому экзамену после сдачи РГР наконец до меня дошло, как с этим работать.
Паскаль был обычный. Сложности — ну вот так у меня видимо мозг был устроен, я НЕ ПОНИМАЛ этого, никак. Первым ступором который занял у меня неделю наверное было i := i + 1
. Если бы была сишная запись с обычным "равно" я бы наверное и две просидел. "как i может быть равно самому себе и ещё единице? Это бессмыслица". Понял, дальше пошло-поехало, с циклами особых бед не было, а потом появились массивы и я снова не мог с ними совладать. Но, в итоге понемногу вошел в ритм и дальше особых проблем не было аж вплоть до ООП.
Зная себя, могу предположить, что у вас был неконкретый ступор, который просто пришелся на этот вот момент времени. Вряд ли это была специфичная именно для этого знания проблема.
В общем скорее непохоже на годный пример того, что «новые концепции всегда сложны». Просто есть простые концепции, а есть сложные (и иногда цена не оправданна).
Хаскель я точно не пробовал.
Не-не. Основная причина в том что бинарный файл получается близко к тому что вы на ассемблере писали. Радоваться htop поэтому можно, но еще можно и дрова писать на расте.
А еще можно веб серверы пилить. Actix-web прямо там и есть.
А еще можно заниматься абсолютно новомодным webassembly.
Ну и тут внезапно Microsoft увлеклась растом. Посему можно гуглить rust winrt.
Да и в ядре Линукса стало ржаветь потихоньку.
Таки уже или все еще в процессе обсуждения?
Смущает, что и время написания может сравниться с ассемблерным. А вот размер бинаря с некоторых пор перестал волновать: размер файлов данных программы и в ОЗУ важнее мне было. Сжатия на лету в памяти, битовые структуры и т. п.
Системное программирование как таковое, драйвера, ядра, инфраструктура мало уже интересует.
Внезапно? То есть то что они 5 лет оплачивали Николаю Киму фуллтайм разработки актикса (и это только то что я достоверно знаю) это тогда что?)
На rust имеет смысл писать что-то что обычно пишут на PHP/Java/C# веба с основной целью перехода — радоваться циферкам в htop?Если вы разрабатываете главную страницу Google – то да, стоит. В остальных случаях это не оправдано. Цена изменения кода на Rust сильно высокая, что идет в противоречие с «эджайл» стилем разработки типичного веба.
Разрабатываю не главную страницу угла а просто сервис авторизации куда постоянно стучатся остальные сервисы. Выиграли по цпу, памяти, не потеряли в общем-то ничего. Не все гугл, но и не надо им быть
Разрабатываем самые обыкновенные веб сервисы, которые можно было бы делать и на Java, например.
Команда довольна, инструментария и библиотек хватает, соседние отделы, насмотревшись на нас, тоже подтягиваются и начинают ржаветь, желания уходить обратно на Java нет.
Цена изменения кода на Rust сильно высокая
Цена чего конкретно? Изменять Rust-код довольно легко и безопасно, так как система типов у языка достаточно мощная и большинство ошибок отлавливает компилятор.
В книге предполагается, что показывается базовый синтаксис, а потом в дальнейших главах объясняется что вот эти типовые части можно прятать за сахар. Видимо авторы считают, что это более честный подход чем сначала показать сахар а потом когда человек столкнется с тем что не 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
habr.com/ru/post/537790/#comment_22574860
Здесь поведение вполне определено, есть одна явная реализация слайсов и функции 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а в го как-то и нет :)
В сообществе го сейчас идёт большая дискуссия на тему того, как именно стоит сделать дженерики, чтобы получить больше бенефитов и поменьше всего испортить/замедлить.
Но ведь для стандартных коллекций дженерики-то по сути есть! А вот пользователю этот инструмент не предоставляется. Если кого-то волнует раздувание кода и медленная компиляция, то он же может просто их не использовать.
В сообществе го сейчас идёт большая дискуссия на тему того, как именно стоит сделать дженерики, чтобы получить больше бенефитов и поменьше всего испортить/замедлить.
Что не помешало накостылить те же мапы максимально мерзким способом.
100-мегабайтное приложение на C# было заменено 564 килобайтами кода написанного на rust. 200 мегабайт памяти которые потребляло приложение на шарпах теперь превратились в 1.2 метра RAM для программы на rust. (Заодно избавился от двух докер-контейнеров в которых это приложение работало).Будь осторожен, это ощущение затягивает и вызывает зависимость! Еще неделю назад ты переписывал на Rust маленькое изолированное приложение, потом снижал счета на AWS, и вот уже организм требует переписать на него Linux kernel или Hadoop.
Выходи почаще на улицу, общайся с друзьями. Помни – ты не можешь сделать весь мир эффективнее.
Пишу раст приложение, которое запускаю в докере (причем не в альпине, в нем проблемисы, а в полноценном дебиане). Видимо я ненастоящий сварщик
Помни – ты не можешь сделать весь мир эффективнее.
Но это не значит, что к этому не надо стремиться!
Т.е. это одна таблица в БД фиксированного формата.
При том что 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 на передачу данных, не нужна тут даже память. Практически все данные можно стримить напрямую из БД.
Просто эталон как не надо решать задачи.
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.
Я обычно начинаю изучение новых языков с перелистывания документации и решения олимпиадных задачек. В расте же я со всей скорости врезался в непонимание ввода данных из консоли. Почему он такой сложный?
Да, батарейки для олимпиадных задач в комплект не положили. Впрочем, несложно сделать утилитку для консольного ввода, чтобы получался код вроде такого:
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?
Есть подозрение, что и будучи переписанным на шарпе профессиональным шарповиком оно бы жило значительно скромнее, нет? Не на расберри, конечно, но вполне рядовой виртуалке.
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 add some_crate
(https://github.com/killercup/cargo-edit) и тоже автоматически получить последнюю версию. Правда, в этом случае надо следить за тем, чтобы не получить альфу будущей мажорной ветки вместо стабильной версии.
Да, только если нужны фичи надо потом идти в файл и редактировать руками. Раньше его юзал когда IDE не умела подсказывать, сейчас не вижу смысла, тем более что с автокомплитом набирать куда интереснее, чем опечататься в одной буковке где-то и получить "такого пакета нет"
Вот недавняя статейка про это, TLDR человек намучался с простейшим API и пришел к выводу, что на вебе ему (пока) не место: macwright.com/2021/01/15/rust.html
TLDR: берется актикс-веб, подключается к нему diesel/r2d2 (хотя если бы я делал с нуля я бы взял sqlx вместо этого), реббит-редис работается так же как и любой другой язык.
Из минусов — отсутствие диая (то есть хендлеры знают, что нужно таскать из хедеров, что из тела, что из кверистринги, такой вот сервислокатор) и отсутствие генерилки опенапи описания по хендлерам: мы руками правим swagger.json и отдаем как статику вместе со swaggerUI.
Что до статьи которая линкуется — хз, мне не показалось что сделать простейшее апи сложнее чем с какимнеибудь экспресс-жс:
Чуть больше конкретики
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 скорее всего процедурные, а там вряд ли IDE может помочь.
Что до sqlx макросов то я в целом негативно отношусь к макросам, которые лазят куда-то во внешний мир, а sqlx насколько я понял из доки лезет в БД чтобы проверить схему. Таким образом, если у меня нет актуальной схемы БД (по любой из миллиону причин) то собрать код я не могу. Поэтому, простой кверибилдер думаю будет предпочтительнее.
Идеальным мне кажется подход EF с code first когда схема БД хранится в коде и в БД может накатываться при желании. Тогда сборке не надо смотреть куда-то, она самодостаточная, но при этом и гарантии типчиков мы сохраняем. Но увы, не все коту масленница.
И хотя rust и не такой сложный как Си в управлении памятью и ссылками
Хм… всё как раз наоборот.
раст как раз появился потому, что управлять правильно ссылками в си — сложно. А если неправильно то неважно насколько это легко
А за этим вот «правильно — неправильно» обычно прячется маркетинг, нет?
Просто зайдите на багтрекер линукса/майкрсофта, да даже хрома (хотя там больше плюсы, но суь та же) и посмотрите сколько там багов связанных с неправильной работой с памятью.
Так что пример с простым управлением напоминает Марка Твена, которому очень просто бросить курить.
Продолжая аналогию — в Расте вы скорее всего вообще не сможете зайти в тубдиспансер. Хотя если вы очень опытный доктор и пять месяцев были на курсах повышения квалификации… То сможете иногда заходить )))
Но это все не про «секреты правильного управления», это как раз подтверждает мои слова — управлять памятью в Си просто, но тяжело, требует внимания.
Остается тогда заключать что самый простой (и, видимо, лучший?) язык это брейнфак, куда проще сишки: всего 6 операторов и никаких странных правил и УБ.
Как говорил один умный человек, сложность: константа, и упрощая язык вы усложняете юзеркод. Я бы предпочел, чтобы за меня пострадали разработчики компилятора, наделав удобных рабочих вещей вроде RAII-борровчекеров, генериков и прочего, а я просто взял и воспользовался.
Можно и другую аналогию привести. В Boeing 737 MAX конструкторы положились на квалификацию экипажа, на то, что экипаж распознает нештатную ситуацию с датчиком угла атаки и примет нужные меры (как язык С полагается на квалификацию программистов). В Airbus сделали мажоритарное троирование датчиков, чтобы не полагаться на экипаж, но экипаж может отключить эту систему (как в managed языках и расте).
Результаты известны.
И вообще пример с MCAS он как раз скорее про обратное, про сахар и «все под капотом сделано умными людьми» — есть все нужные подписки, память освобождается где нужно, думать не требуется. Ну вот и не подумали потом в продакшене…
В эрбасе под капотом сделали сложно, но надёжно. И если что не так, то по индикации всё видно.
MCAS — под капотом всё просто (предельный угол атаки на одном датчике? корректируем), прямой индикации наличия проблемы нет, экипаж должен быть начеку и всё понять по косвенным признакам.
Короче аналогии нас куда-то завели…
Ту-154? Третий по аварийности в мировом рейтинге? Вроде всё правильно: не стоит рассчитывать что люди всё будут делать безошибочно, если их не подстраховывать автоматикой. Ну и не стоит рассчитывать, что люди будут использовать автоматику, если она доставляет им малейшие неудобства, если её им не навязать.
Мда, короче и тут аналогии увели нас совсем в другую сторону. Зря я это начал…
Нужны абстракции правильной толщины — если абстракция слишком бедная, то ей пользоваться сложно (не с точки зрения предсказания, какой получится результат выполнения этой одной строчки, а с точки зрения того, как эта одна строчка влияет на всю программу), потому что она позволяет слишком большим количеством способов отстрелить себе ногу.
А если слишком богатая, то оказывается сложно предсказать поведение системы «под капотом» (например, в случае управления памятью это будет сборщик мусора, который добавляет новые задержки, над которыми нет контроля).
Чистый ассемблер уже является тьюринг-полным.
Да, и поэтому связка С+C# в принципе решает все задачи, стоящие перед человечеством.
И как же эта связка решает задачу обработки ошибок?
Если мы про проблемы с управлением памятью.
Нет, я про ошибки в принципе. Навроде "Файл не удалось прочитать" или "Число не удалось распарсить"
В каких других? На ошибки в расте похоже? В хаскелле? В каком-нибудь erlang?
Только вот проблемы могут по-разному выражаться и по-разному комбинироваться.
В том, что раст дает безопасность "функция всегда вернет результат и ошибки не будет" на уровне сигнатуры, а джава — нет. В джаве вы функцию int Foo(int x) => x*x
можете переделать в
int Foo(int x) {
if (x == 0) throw new SomethingException();
return x*x:
}
А в расте подобное изменение — ломающее и приведет к ошибке компиляции (т.е. невидимый эксепшн в продакшн не пролезет таким образом). Ну и так далее.
Я специально выше написал — ну ок, будут у вас вместо исключений коды ошибок. Какая разница? Если для вас важно, что Х не может быть нулевым, то вы либо бросаете исключение, либо возвращаете nullable-тип, возьмем эти два варианта. еще раз — если для вас это важно, то вы сообщаете это из функции и обрабатываете это. Если неважно — не обрабатываете.
Почему мы это обсуждаем?
Такая что добавление необрабатываемого кода ошибка приводит к ошибке компиляции. А добавление нового вида эксепшнов — к "упс" на проде
У вас нет никакого способа сказать что функция Foo
бросает вот эти исключения. Точнее есть — чекед эксепшны, тока ими никто не пользуется. И нет, не потому что сама идея проверки возможных ошибок в компайл тайм порочна, а потому что сами чекед эксепшны слабы. Обсуждалось уже не раз.
В случае с честным словом, статанализатором (который повторяющиеся куски кода покажет) и очевидным в данном случае код-ревью хотя бы кто-то вас поймает за этим делом…
Соглашусь, что явная лажа в обработке ошибок видна на кодревью лучше, чем отсутствие оной, но статанализатор все равно еще лучше. В итоге имеем, что старый процесс в общем остался тем же.
Я еще вот что поясню — мне ваша общая претензия к исключениям понятна, в этом смысле я на одной стороне баррикад с тем известным китайцем из МС. Но скорее поднимать это на щит я не готов. А тем более вспоминать про это при обсуждении механизмов управления памятью…
И, кстати, в статье от авторов 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.
Каждая отдельная команда управление памятью — проще, конечно, в С.
Но для не-тривиальной программы на С, следить (руками) за всеми инвариантами (чтобы не получить use after free, double free, etc) уже сложнее (хотя каждый отдельный примитив по-прежнему проще).
И тут мы снова начинаем обсуждать, что такое нетривиальная программа и зачем она такая нужна вообще.
А вообще я зря начала эту ветку обсуждения, прошу прощения, можно ее закрывать. Она к этой статье не к месту.
Ибо не всем понятно что за то, чтобы иметь одновременно безопасность и управление памятью надо чем-то платить.
А чем, собственно говоря, платить?
Раст за счет совершенно другой концепции обеспечивает еще и скорость, но сложность возрастает критически…
Тезис примерно такой: Написать большое приложение на С так, чтобы там не было проблем с памятью сложнее, чем написать приложение на Расте, чтобы там не было проблем с памятью.
Несмотря на то, что написать однострочник на С, конечно, проще.
Просто для сравнения нужны примеры, и у Си они есть, а у Раста мало что вообще есть.
И она в принципе довольно сложна, да. И как-то получилось, угу.
Не понятно, получилось ли — скорее всего, в ней есть проблемы с управлением памятью (и ссылки на уязвимости, с этим связанные, — это довольно сильный аргумент за это).
В то, что на С написать программу, которая иногда работает проще, чем на расте написать программу, которая иногда работает — никто не спорит.
Симмеетричный — это написаная ОС? Пожалуйста, есть redox.
Что до утверждений "да вы посмотрите на си вон линукс есть и Integrity" — если на протяжении 40 лет вливать миллионы (миллиарды?) долларов и тысячи человеколет в проект то можно и на брейнфаке его завести. Качество языка это показывает никк, кроме того, что когда их писали 40 лет назад из альтернатив был паскаль и лисп. Только эти годы индустрия не спала и что-то придумывала. Аргумент "деды писали и нам велели" такой себе
Ваши исходные пойнты — безопасность и управление памятью. Это все делается и в Джаве например
В Java есть ConcurrentModificationException, которое в Rust просто не требуется.
Я просто не пойму, куда клонит комментатор выше. Разговор выгляди так:
1) Ручное управление памятью — фу.
2) Берите джаву, там не ручное.
3) В джаве эксепшоны.
4) Ииии… чо?
Эксепшоны — механизм _для всего_, не только для решения проблем с памятью, но и с ошибками арифметики, выходом за границы (есть много где, и везде не бесплатно), таймаутами и черт знает чем. Когда у вас в рантайме из-за 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. Разница в том, что я узнал про все места которые нужно поправить от компилятора и до того как собрал приложение, а не в продакшне.
2) Что значит application_url возвращал? Мне кажется, application_url это какой-то объект, что он может возвращать? Возвращает функция, а единственная функция что я вижу это as_str()
3) Что такое «появилось возможность ошибки»?
В общем, этот пример явно не идеал выразительности языка
Да, StopIteration exception
это просто какая-то насмешка над принципом "используйте исключения только для важных исключительных ситуаций"
Да, естественно, не все языки и подходы используют их правильно. Но, как говорят некоторые комментаторы, «нормально делай, нормально будет». Не стоит использовать плохие примеры, пользуйтесь только хорошими
try {
} catch (MyException1 e) {
} catch (MyException2 e){
}
и даже так
try {
} catch (MyException1 | MyException2 e) {
}
Вопрос: как мне во время компиляции узнать какие эксепшны бросает функция? Единственный ответ, который есть у джава: чекед эксепшны, от которых все плюются. То есть ответа по сути и нет. В чем и проблема.
Неплохая статья на тему: http://www.lighterra.com/papers/exceptionsharmful/
В функциональщине тоже самое, нет одного типа Result, равный LineNumber | Error1 | Error2, есть класс Either, который является структурой LineNumber | (Error1 | Error2), что явно подчеркивает разницу между нормальным ходом выполнения программы и исключительным
А дальше, при попытках обработать эти Error-ы начинаются разные неприятные траблы типа конвертации одного Error-а в другой, добавления нового Error-а и т.п…
Потомучто в exception-ы нужно засовывать действительно исключительные ситуации.
Все это говорят, но никто так не делает. Отсутствие ключа в словаре — исключение. Не получилось попарсить строку в число — исключение. Файл не найден — исключение. Сервер вернул 404 код — исключение (хотя может я бы предпочел получить какое-то дефолтно-пустое значение? Не, не нужно).
Мантра хорошая, в реальном мире только не работает.
Как по-вашему это должно работать?
Прелюд хаскелля плох, это ни для кого не секрет :shrug:
хед в идрисе так себя не ведет: https://github.com/idris-lang/Idris-dev/blob/master/libs/prelude/Prelude/List.idr#L128
Не копировать безумные решения которые иногда встречаются среди стандартных компонентов. В расте есть либы thiserror/anyhow которые инкапсулируют в себе обработку ошибок в либах/приложениях соответственно.
Просто берете и пишете, это несложно.
В каком любом? В хаскелле так и работает во всех приличных либах (да, прелюд говно и неприличный). В скале в этих ваших котоэффектах так же.
В остальных мешает:
- то что стандартная библиотека не следует этому. Писать врапперы всех используемых стандартных и просто популярных библиотек — титанический труд, и как правило оно того не стоит.
- то что в языке нет обычно удобных средств для комбинирования ошибок, взвратов и т.п., тот же Try трейт из раста. Без монадической записи работать с ошибками становится портяной из
Match(...)
, разработчики знакомые с LanguageExt передают привет.
А другие библиотеки ведь тоже пишут люди. Кто-то же написал нестандартную библиотеку под хаскел? Что мешает сделать тоже под другие языки?
Не исключение, а завершение процесса с ошибкой, как и в любом случае, который чаще всего является багом. Если выход за границу массива — штатная ситуация, нет проблем, пишите a.get(10)
и обрабатывайте проблему явно.
В любом другом, где есть проверяемый во время компиляции Option/Maybe/etc. — ничего не мешает.
Отсутствие нормального типа для возможности выразить опциональное значение
Нужно, чтобы все либы его использовали там, где значение может отсутсовать, иначе смысла в нем нет. В шарпе тот же NRT большая отдельна фича которую несколько лет пилили, и та костылямисбоку от Nullable прилеплена.
Её не везде используют потому что сначала сделали "как в си", а когда решили подумать головой оказалось что 3 billion devices are running Java. Щто поделать, исторический процесс.
Конкретно в 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]
}
}
Выразить безопасную индексацию без использования зависимых типов, вроде как нельзя.
Помнится, было у нас тут одно извращение на эту тему...
Я выше только что ответил на вопрос про эксепшны в джаве, смысла писать, предлагаю в 1 месте все же это обсуждать.
И да, там был не греп. Точнее, не только греп. Был просто поиск ошибки, как поиск любой другой ошибки. Если бы я заранее знал, что «вот здесь я забыл убрать заглушку», то мне бы и ошибку искать не пришлось
На любом языке придется работать с чужим кодом. Если конечно, этот язык достаточно популярен, чтобы встретить другого такого человека… ах вы хитрец… :)
> Так надо было просто прогрепать же.
Дык откуда я знал, что нужно прогрепать? Я несколько часов вносил разный код. И понадеявшись на «мощную систему типов std::variant-а» о собственно этом варианте подумал в одну из самых последних очередей
Да, поэтому чем больше гарантий даёт язык, а не всякие там coding style guidelines и кодревью, тем лучше.
Любой язык можно превратить в javascript при должном желании
Ну так пишите [](NewlyAddedType) { /* TODO */ } хотя бы и грепайте потом по TODO
Ну почемуто у меня это не прижилось. но в любом случае коммент TODO не относится к развитым типам
В случае идриса стиль — это отсутствие unsafe. Вы почему-то наивно пологаете, что стоит вам только дать программисту писать на идрисе, то он сразу принесет вам полностью верифицированное доказательство. Но в реальности, вы дадите программисту задачу на полгода, и через полгода выяснится, что в его коде куча unsafe. Вы дадите ему 2 месяца на избавление от всех unsafe, но еще через полгода заметите, что дело не сильно сдвинулось с мертвой точки. И тут вы встанете перед выбором — либо заливать то, что есть, либо давать еще пол-года без какой-либо гарантии результата
Какой стиль мне нужно установить и где, чтобы проверять только маленькую часть кода на плюсах? И какую, кстати?
Зависит от задачи. Если хочется вообще упороться по тойже безопасности с памятью, то можно только shared/week_ptr-ы использовать исключительно везде. Если же хочется более гибкой производительности, то будет сложнее. Тот же CppCoreGuidline можно попробовать например.
И да, C++ наверно оди из худших языков в этом смысле. А в какой-нибудь яве с этим может быть даже проще.
Рад, что мы сняли вопрос о возможности гарантировать отсутствие ерунды в коде и перешли к вопросу о том, какие программисты бывают.
Я на это с первого сообщения пытаюсь намекнуть
Хорошо бы иметь возможность локально переключать ошибки тайпчекера в ворнинги, не блокирующую дальнейшую работу. Но что-то подобного нигде не встречал.
Возможность не вернуть ничего, когда по сигнатуре функции нужно вернуть? Это todo! или unimplemented! в расте поддерживает.
Вызвать функцию, которой нет у типа, который вызывается? Кажется, такое не нужно выключать — это гарантированно упадёт в рантайме.
Больше про второе. Да, оно гарантированно упадёт в рантайме, если когда-нибудь вызовется. Но могут быть нетехнические гарантии, что или не вызовется (в UI вообще нет), или что падение не будет считаться багом (нефиг годовой отчёт в мае составлять).
Ну вот есть todo — упадет в рантайме.
Попытка построить годовой отчёт на незаконченном годе — это как раз те гарантии, которые дают правильные типы — завести тип «законченный год», и пытаться привести к этому типу перед попытками посчитать отчёт (и иметь понятную ошибку, если не приведётся).
Она возвращает ровно тот тип, который от неё ожидают. А в рантайме падает, если выполняется.
Выглядит примерно так: play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=a5fb23799622af07da51280faa873479
В идрисе кстати сделано так, что и падает, и ещё и ожидаемый тип подсказывает (А не просто боттом). Оч круто. В расте увы, решается разве что тулингом который при попытке закоммитить такйо код сделает ворнинг что в коде найдены тудушки и неимплементированные куски
Из забавных вещей — с 1.40 появился макрос todo, который делает то же самое: doc.rust-lang.org/std/macro.todo.html
Да может он все вывести, попробуйте вместо unimplemented!()
сунуть какой-нибудь ()
он мигом скажет, какой тип вывел. Не обязательно конкретный прям тип, но он может хотя бы намекнуть что ожидается Arc<Vec<{unknown}>>
Туду вроде ввели просто чтобы писанины меньше было. Хз, пусть будет, не мешает вроде.
Как найти все места где после вызова функий не обрабатываются возможные ошибки? Как понять, это разработчик не знал что там возможно исключение или знал и просто решил что на текущем уровне он может только прокинуть ошибку выше?
В любом случае, я слабо себе представляю нормальную программу, в которой исключения ловятся и тупо подавляются, без записи в лог или куда-либо еще. Вроде это на первом уроке любого института говорится «Не давите исключения»
Я не спорю, что этот 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() просто не скомпилируется).
Впрочем, я не видел каких-то нормальных программ, целиком использующих этот подход. Может он и правда на порядок лучше, а я об этом просто не знаю
Покажите любой код на расте где начинается ад, будет что обсудить предметно :shrug:
Во-вторых, нужно сойтись на том, что считать адом. Точнее, что считать НЕадом. Я правильно понимаю, что НЕадом вы считаете использование исключительно возвращаемых значений, без 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-ы
Вероятнее всего (сам не проверял) — в тех местах, где невыполнение условия может означать только наличие бага внутри самой библиотеки, и, как следствие, о нём необходимо узнавать при первом проявлении.
А даже если из представленных выше библиотек смотреть, то например вот
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 это явный признак программерской ошибки, которая должна всплыть при первом же тестовом запуске приложения.
В одной картинке
И повторюсь — мне, укушенному защитным кодингом в эмбеде, коды возврата по душе. Я отстаиваю исключения сугубо из технической справедливости.
С управлением памятью — никак. С безопасностью — напрямую.
Тем более что даже «проклятый русский язык» не дает мне понять, что вы понимаете под термином «безопасность». По меньшей мере три варианта я мог бы предложить.
Если их понять — собрать безопасное низкоуровневое приложение становится относительно просто.
В С — базовые операции гораздо более простые, но собрать безопасное приложение поверх них — очень сложная задача.
В расте их не надо понимать, чтобы собрать безопасное приложение. У вас просто не получится собрать небезопасное, вне зависимости от понимания.
Речь о том, что, если их не понимать, то собрать хоть что-то получится только случайно.
А вот чтобы собрать безопасное — уже нужно (хоть до какой-то степени) понимать, потому что иначе не получится собрать ничего.
Насчет автодокументации пакетов. Недавно писал пет-проджект на Rust с текстовым интерфейсом, пользовался крейтом tui. Апи на самом деле прост, но документации не написано практически никакой, лишь пара примеров на главной странице доков. Однако при ошибках линтер сразу бьет по рукам: строжайшая система типов не дает послать неправильный аргумент куда попало или оставить Result нераскрытым.
Так вот когда я этот код писал, понял, что не представляю, что бы делал в такой ситуации, например, на пайтоне или JS. Мало писал на динамических языках. Если там есть линтер, то он смотрит внутрь функций? Не знаю. Но Раст действительно радует в те моменты, когда не пропускает ошибки дальше компиляции.
И не радует, когда понимаешь, что тебе нужна shared-логика и появляется выбор: переписать все так, что архитектура таки ляжет на модель владения или начать наворачивать Rc и RefCell.
Сам недавно начал читать Rust book, потому что прочитал статью про Redox OS, написанную на Rust.
Не согласен с тем, что 2 глава, где сходу разбирается игра про угадывание числа должна гореть в печи.
Мне наоборот зашло, что тебе с самого начала дают «пощупать» язык, без многостраничной теории перед этим. Да, ты не всё досконально понимаешь, что в этом коде происходит, но в общих чертах всё же понимаешь.
Плюс в introduction написано, что во 2 главе мы вам дадим возможность пописать код, а если вы не хотите «нырять» в код сейчас, то пропустите её и прочитайте после 3 главы.
Rust классный, сам бы хотел на нём пописать что-то высоконагруженное.
Но, как автор сам и замечает, в конкретно его задаче смысла было переходить на Раст было совсем немного — в основном все ресурсы потребляли другие плохо настроенные контейнеры вроде Postgres и Elasticsearch.
Учитывая современные реалии с Докером, всё, что сэкономил автор по сравнению с .Net Core — сотню мегабайт на диске, которая к тому же была бы размазана по разным будущим приложениям, т.к. базовый образ они используют общий.
И исходя из описания, никаких сложных расчётов программа не производит, то есть и по CPU выигрыш должен быть близок к нулю.
В целом, да, Раст — это интересно и я согласен с этой цитатой автора:
Я снова почувствовал себя так же хорошо как в детстве, когда я запускал свои приложения на ассемблере и видел, как какие-то 20 килобайт единичек и ноликов создавали поразительные визуальные эффекты на экране. Rust это весело.
но давайте будем честны, мы начинаем тащить в прод то, что просто нам нравится. Сам подход не очень далёк от действий лаборанта, который тоже добавлял то, что хотелось, хотя особой надобности в этом не было никакой.
Просто в этот раз общее умение автора разрабатывать системы помогло ему не привести всё в то же исходное состояние.
Но вот по сути ценностного предложения я вижу некоторые манипуляции. Когда-то очень-очень давно я делал приложение, которое делало буквально почти то же самое — скачивание данных с сокета (от АСУТП) и отправку в постгрес-базу. Я крайне плохо помню детали (даже не могу вспомнить, как визуально код выглядел), но это был C++ Builder 6.0, графическое приложение, сокеты виндовые, какие-то несложные поисковые запросы к базе с выдачей на форму и сохранением в файл. И это вместе с встроенными в исполняемый файл либами Билдера занимало единицы мегабайт, как и все мелкие софтинки, которые я тогда писал в Билдере. И хотя, возможно, у вас все же было больше функционала, но тем не менее есть ощущение, что с учетом моего примера противопоставление Раста конгломерату адских сущностей выглядит несколько однобоким.
Спасибо за статью. Очень интересно было читать! Особенно от человека, понимающего "EB FE тебе в EIP" ;-)
Очень любя изобретать свои "велосипеды" на голом железе, микроконтроллерах и FPGA ради развлечения — я теперь знаю, что я хочу поизучать дальше! Спасибо)
Как мы ржавели. История внедрения и обучения