Комментарии 335
А как в сравнении с Haskell?
Отлично, на самом деле. В Haskell что ни говори, тяжело писать большие приложения. Rust берёт же от хаскеля много хорошего, и при этом даёт уверенность, что у тебя не утекают от лени thunk-и, что скомпилированный код скорее всего отлично работает, что твои попытки оптимизации работают (привет, мемоизация и прочая хаскелевская муть). Rust - это быстрый Haskell с императивностью и мутабельностью в нужных местах и по делу.
Ну нет, в Расте нет типов высшего порядка
Это не на Haskell тяжело писать большие приложения. На Haskell их легко писать за счет того, что язык очень сильно помогает в рефакторинге кода.
Хаскель отлично помогает своей системой типов, само собой. Но он слишком сложный для большинства программистов. Память из-за ленивости бывает сложно понять куда уходит и что с этим делать. В половине библиотек изобретают операторы, которые делают код компактнее, но приходится учить все эти закорючки, чтобы даже просто читать код.
В планах на изучить Haskell есть (может еще F#), но скорее для того, чтобы посмотреть ~~есть ли жизнь на марсе~~ как живется в полностью функциональном языке без доступа к мутабельности, может быть позаимствовать какие-нибудь подходы.
И там и там 2 вакансии, так что равны +-
Кстати нет. Не так давно видел вакансии на Rust в Сбере, МТС и Тиньке помимо мелких компаний и стартапов. Ну и зарубежных удаленных еще больше. И поскольку язык молодой требований из серии 5-10+ лет опыта на нем нет.
На Rust вакансий все же раз в 20 больше, чем на Haskell. С другой стороны количество Rust-истов гораздо больше Haskell-истов.
Таки Rust лучше C/C++ ? Или для каких-то конкретных применений? UB это на самом деле так страшно или, к примеру, в Arduino с ним никогда и не столкнёшься?
UB это на самом деле так страшно или, к примеру, в Arduino с ним никогда и не столкнёшься?
UB это страшно. В том числе и потому, что оно может быть в коде, но вы с ним не "столкнетесь" до тех пор пока не обновите компилятор или его опции, не поменяете что-то в другой части кода или просто не сложатся звёзды на небе случайным образом. От платформы это слабо зависит.
и оно может быть в коде компилятора, поэтому два откомпилированных бинарника с одного и того же кода могут быть разными. Но и в расте до... UB. Не говоря уже о уязвимостостях заложенных в генерацию кода, особенно с borrow. Забавно что на расте в отличии от плюсов гораздо легче сымитировать ошибки с обновлением кэша.
Ну хотелось бы примеров для того же условного ардуино, где можно случайным образом задействовать UB, особенно учитывая что это по сути embedded разработка, где каждую переменную выверяешь и действительно вручную проверяешь память чтобы не забилась, потому как там её кот наплакал. И взаимодействие там real-time ибо исполнительные механимзы, так что в проде это чревато не просто каким-то неработающим сервисом, а поломкой всей системы, причем в механическом смысле тоже.
UB - это не обязательно только про обращение к неинициализированному или некорректному участку памяти. UB - это, например, переполнение знаковой интовой переменной. Да, вы можете предположить, что есть у вас signed integer и вы прибавляете к нему +1, то компилятор просто сгенерирует процессорную инструкцию инкремента или сложения, и зная как представлены числа на архитектуре предположить, что если у вас есть значение в переменной INT_MAX, вы к ней прибавите +1, то получите INT_MIN. А потом вы где-нибудь напишете if (X+n)>X, чтобы проверять не вызовет ли операция переполнение. И оно даже скорее всего так будет работать на протяжении многих лет. Но проблема в том, что стандарт языка не даёт таких гарантий! Вообще не даёт. Более того, согласно стандарту языка, такой код вообще не является корректным кодом. И если компилятор обнаружит а коде что-то подобное, то он имеет полное право в таком случае сгенерировать вообще все что угодно - вплоть до того, что этот if будет выдавать и true и false в разных частях кода для одинаковых значений X и N (пример там есть по ссылке), компилятор также что выкинуть какой-то находящийся рядом код или сгенерировать инструкции которые делают вообще не то что вы подразумевали. И то, что он не делает это сейчас, совершенно не гарантирует, что это не произойдет когда-нибудь в будущем (стоит вам обновить компилятор, изменить опции компиляции или даже поменять что-то в коде по соседству). А если у вас будет что-то типа такого в условии цикла, то бесконечный цикл может перестать быть бесконечным, или наоборот (см. пример).
И это только один из сотен примеров. UB - это не только про память и многопоточность, увы.
Довольно подробные объяснения с примерами можно найти тут:
Неопределенное поведение и правда не определено
Неопределенное поведение может привести к путешествиям во времени
Простите, видимо неверно сформулировал вопрос, опять же касаемо конкретных применений. То что вы пишете, скорее всего имеет место быть в языке вообще, но в конкретном применении конкретной платформе это может быть не так. Вопрос был про Wiring и avr-gcc, где даже многозадачность делается костылями вроде программного таймера, основанного на аппаратном с типом unsigned long (uint32_t) и такие костыли даже переживают переполнение переменной в исходной функции. Но эти фишки известны до компиляции и как раз эксплуатируют такое поведение. Это я возвращаясь к вопросу о том как же можно "случайным образом задействовать UB" в конкретной области применения. А конкретная область применения это статическая линковка и известность того что может прилететь с датчиков на входы и нет никаких внешних сервисов с которых может прилететь что-то недопустимое. Вот и интересно, в таких жёстко заданных платформой рамках, возможно ли столкнуться со случайным UB или это настолько маловероятно, что бояться этого не стоит.
То что вы пишете, скорее всего имеет место быть в языке вообще, но в конкретном применении конкретной платформе это может быть не так.
Нет, нет и ещё раз нет. UB не зависит от платформы или применения. Вообще не зависит - UB в коде либо есть, либо его нет, вне зависимости от платформы.
Случаи undefined behavior описаны в стандарте языка, их там несколько сотен. Код, в котором есть UB по определению не является корректным кодом на языке C или C++. Более того, в случае наличия UB в коде компилятор имеет полное право сгенерировать любую хрень из вашего кода, и это вообще не зависит от аппаратной платформы и ее ограничений.
Вы, кажется, не совсем верно понимаете, что такое UB. UB это не "я обращаюсь к неинициализированной переменной в куче, но на моей платформе память изначально заполнена нулями, значит в ней будет ноль". UB - это то, про что в стандарте языка явно сказано "так делать нельзя", и если компилятор увидит такое в коде, он, например, может вообще выкинуть условие if (a == 0) и все что внутри него. Имеет полное право. Если на вашей платформе инкремент знакового двухбайтового инта 32767 сделает его равным -32768 (на большинстве платформ так), то увидев if ((x+1)>x) в коде компилятор может выкинуть эту проверку и заменить везде на true, потому что согласно стандарту языка переполнение знаковых недопустимо. И так далее. Наличие или отсутствие UB в исходном коде не зависит от платформы, под которую вы компилируетесь.
Ну и полагаться на "у меня в коде UB, но для моей платформы компилятор генерит правильный машинный код" очень опасно. Сегодня генерит, а завтра что-то изменится, и уже не будет. Потому что имеет полное право.
Как я понимаю, UB делалось ради переносимости кода между платформами, оставляя конкретное поведение на совести компилятора. Соответственно если конкретная платформа убирает UB из своего компилятора, то это делает программу непереносимой, а раз так то программа уже не будет называться написанной на языке "Х", потому возникает диалект языка, привязанный к платформе, и назовут этот диалект языком "Y". Тогда и получим отсутствие исходного UB языка "X" в языке "Y", несмотря на то что по сути это один и тот же язык, просто этот исходник не скомпилируется другим компилятором под другую платформу (или скомпилируется, но выдаст то самое UB про которое вы говорите, но делающий такое уже ССЗБ). Если же, как вы говорите, поменяется что-то в компиляторе, то скорее всего либо разработчики компилятора будут опираться на предшествующее отсутствие UB либо уже железка будет переделана под новое поведение.
Конечно же это всё может быть и совсем не так (всякое случается), поэтому будет интересно, стоит проверить приведённую вами конструкцию UB в конкретной среде.
Да, но нет. Делалось-то для переносимости, но это совершенно не означает, что на конкретной платформе все UB оказались до-определены.
Компиляторы с удовольствием пользуются некоторыми видами UB для оптимизации кода; в таком случае поведение остаётся неопределённым даже если зафиксировать платформу и версию компилятора.
По-моему в C изначально UB именно для этого и было и означало скорее implementation defined поведение. Т.е. язык как бы не при делах, но в конкретных условиях все ок. В те времена C еще можно было считать человекочитаемым ассемблером.
Сейчас же UB предназначено целиком и полностью для оптимизации, чтобы у компилятора была свобода ломать код так, как ему вздумается. Платформа здесь не имеет значения, потому что компилятор не смотрит на то, что в какой-то системе two's complement представление чисел. Для него UB это UB на любой платформе. Собственно, для него вообще платформы конечной не существует - он работает с абстрактной машиной. Поэтому как выше писали, проверка на переполнение будет просто удалена компилятором. Поэтому и версия компилятора может запросто сломать то, что работало раньше, если оно опиралось на UB.
оставляя конкретное поведение на совести компилятора.
Неа. То, что вы сказали - это не undefined behavior, а unspecified behavior, в стандарте оно тоже есть.
если конкретная платформа убирает UB из своего компилятора
Почти, обычно UB "убирает" не конкретная платформа, а конкретный компилятор. Например GCC и Clang делают исключение (точнее, расширение языка) для упомянутых выше union, разрешая в них puning простых типов. Но пользоваться таким можно только если разработчиками компилятора явно явно сказано "в стандарте C++ это UB, но мы реализуем нестандартное расширение языка, в котором это не UB". Если такого не сказано, то писать код таким образом под этот компилятор нельзя.
просто этот исходник не скомпилируется другим компилятором под другую платформу
Скорее всего скомпилируется, нередко даже без варнингов. Но никаких гарантий, что он всегда будет работать правильно уже не будет.
В этом и опасность UB - вы можете даже не подозревать, что оно есть у вас в коде до определенного момента.
Если же, как вы говорите, поменяется что-то в компиляторе, то скорее всего либо разработчики компилятора будут опираться на предшествующее отсутствие UB
Это только в том случае, если разработчики специально "определили" это конкретное UB и больше не считают его UB в своей реализации. А если в прошлой версии это UB было именно UB, то в новой версии тоже возможно все что угодно. То, что работало, может перестать работать. А может наоборот начать работать то, что раньше не работало :) на то оно и undefined
стоит проверить приведённую вами конструкцию UB в конкретной среде
Это ни о чем не скажет. Оно может отлично работать в вашей конкретной среде. До тех пор, пока вы не поменяете что-то в опциях компилятора, не обновите компилятор до более новой версии, или даже не поменяете что-то в какой-то другой части исходника.
Для того, что вы описываете, в стандарте есть отдельное понятие - unspecified behaviour (да, тоже UB, но обычно когда пишут UB, подразумевают undefined behaviour). Наличие в коде unspecified behaviour означает потенциальные проблемы с переносимостью, но просто от обновления компилятора или изменения никак не связанного участка кода ничего не сломается.
А undefined behaviour это именно "программа, допускающая такое поведение, некорректна". Я слышал, что изначально при стандартизации комитет просто не захотел принимать никаких решений о том, какое поведение должно быть в этих ситуациях, а потом компиляторы научились делать крутые агрессивные оптимизации, исходя из того, что UB не может происходить, и в результате наличие в стандарте UB стало принципиальной позицией.
Почитал статью и так и не понял чем раст лучше, наверно это неплохой язык, но что мешает использовать пул потоков в с++, почему автор статьи считает что многопоточное программирование на с++ это потоки и мьютексы?
Мое понимание разработки на данный момент сводится к тому что можно очень долго выяснять какой язык лучше и чем на, скажем так, академических примерах, беда в том что это сильно далеко от реальной жизни. Мне не нужен язык, мне нужно программу делать, а для этого нужны библиотеки, очень много библиотек, на данный момент в составе vcpkg около 2,5 тысяч библиотек. Это код который у вас сразу и скорее всего без проблем соберется под все распространённые настольные и мобильные платформы. Не представляю что должно случиться что бы библиотека CGAL была переписана на другой язык. В нее закопаны наверно сотни человеколет работы. И это только одна библиотека из множества других. И такие библиотеки можно перечислять очень долго: SQLite, Qt, curl, boost graph, openimageio, eigen3, lua, imgui... У меня в глазах ужас при мысли как каждую из них можно было бы портировать на другой язык, не говоря уже о всех вместе.
В последнее время пишу в основном фронтенд на js. В реестре npmjs.org более 2 миллионов пакетов. Тем не менее пишу сложные веб приложения используя только 2-3 из них (кроме фреймворка), да и от тех при желании можно избавиться. Те, кто использует много библиотек через пару лет, имеет огромные проблемы с апгрейдом и поддержкой приложения
В С++ не так?
Плюс минус так же, много библиотек в проект не затянуть. Автор комментария скорее ведёт к тому что Раст просто беден на качественные библиотеки что смогли бы закрыть потребности программистов, как с этим сейчас успешно справляются плюсы
Кстати, раз уж про поддержку зависимостей заговорили. В Rust есть потрясающая возможность иметь одну библиотеку нескольких разных версий без конфликтов (если не надо между разными версиями взаимодействовать). Так что если ваша зависимость A имеет подзависимость C версии 1.0.0, а в зависимости B подзависимость C версии 2.0.0, то они спокойно продолжат работать. Можно даже импортировать свою собственную библиотеку другой версии (это используется, например, чтобы пофиксить баг только в самой новой версии, а в старых просто импортировать из новой и не поддерживать несколько версий фиксов).
Звучит, так, как будто это отличная фича, чтоб выстрелить себе в ногу.
Нет, на самом деле это офигенно и позволяет продолжать писать код, даже если прямая зависимость А уже обновилась чтобы использовать косвенную зависимость С версии 2.0, а зависимость B ещё на 1.0.
В плюсах это, я думаю, сразу ставит крест на такой комбинации - форкайте B и переписывайте на 2.0 или откатывайте А на старую версию.
Насколько я знаю, есть одну неожиданную проблему, которую это может вызвать - если все же библиотеки A и B обмениваются типами из C, то можно получить ошибку expected struct C::InnerType, found a different struct C::InnerType; note: perhaps two different versions of crate
C are being used?
В остальном, насколько я знаю, проблем это не вызывает.
Много раз сталкивался с такой проблемой в других языках.
Например библиотека А имеет зависиость на B 1.0, а C ссылается на B 2.0. Всегда было больно, из всех моих случаев поддержка разных версий зависимости решило бы проблему(в часте случаев так и фиксилось - пинилась старая версия какими-то костылями)
Проблема есть, я ж не отрицаю.
Но не будет ли больно искать багу, когда окажется что в каких-нибудь тестах и в разных местах программы, с виду, одинаковый код, который, совсем неочевидно, будет работать по разному ?
Можно конечно будет сказать — это %somecoder% во всё виноват, а не инструмент, но время будет потеряно.
Нет, не так, с++ очень стабилен в поддержке и всегда имеет обратную совместимость(был один или два случая когда это правило нарушилось). Наверно есть библиотеки которые могут перестать работать, но таких случаев не помню. Многие с++ библиотеки слишком большие что бы быть заброшенными. Например curl это часть операционной системы линукс и с некоторых пор виндовс. Qt развивается с 93 года или что-то около того. Многие библиотеки с++ это матрешки, которые зависят от более мелких библиотек, а те еще от более мелких. К примеру openimageio зависит от 98 других библиотек. Так же в с++ очень мало зоопарка библиотек, никто не будет писать аналог zlib(библиотека сжатия данных).
Бегло посмотрел репозиторий что вы дали, нашел интересную штуку JoltPhysics.js( A WebAssembly port of JoltPhysics) это то о чем говорил, с++ проникает условно в мир других языков через порты и обертки под эти языки, уверен есть порт для питона и других языков, но внутри там будет с++ библиотека.
Еще почему-то в подобных разговорах принято считать что программисты с++ думаю только о нем и ничего вокруг не видят, в "мире с++" вполне нормальная практика брать и вкручивать в приложение другие языки, почти все игровые движки созданные на с++ имеют внутри себя интерпретаторы(или jit компиляторы) других языков для упрощения написания логики, как правило это lua или python, Unreal Engine внутри содержит графический язык BluePrint. Qt сейчас переходит на QML это использование с++ для базовых функции и склеивание их через js.
Так же нет проблемы двигаться вниз по языковым абстракциям и спускаться до ассемблера, если есть желание использовать какие-то низкоуровневые процессорные расширения типа AVX, почти все видео кодеки туда спускаются, но это порождает определенные трудности в переносимости под другие типы архитектур.
Почитал статью и так и не понял чем раст лучше, ..., но что мешает использовать пул потоков в с++
Видимо у меня не получилось расставить акценты в нужных местах. Преимущество Rust не в том, что есть библиотека, в которой пул потоков есть (и даже не в идиоматичном варианте, когда для того, чтобы сделать код многопоточным надо добавить .par_bridge().into_par_iter()
и все), а в том, что компилятор гарантирует отсутствие гонок данных. Этот пример важен не самим кодом, а текстом после, т.е. пока я писал этот пример я сделал много разных ошибок, которые бы не помешали скомпилироваться C и/или C++ коду, но помешали скомпилироваться коду на Rust. Сила Rust в гарантиях компилятора, который позволяет экспериментировать. В safe невозможно скомпилировать программу там, чтобы передать одну задачу в 2 разных потока и потом дебажить гонки данных (можно послать копию задания, но это будет просто бесполезной работой, а не UB). Я могу позволить себе попробовать написать код без мьютексов, т.к.если я сделаю в нем ошибку, код просто не скомпилируется. В большинстве других языков конкурентность связана с огромным количеством граблей, которые приходится ловко обходить.
curl
Curl не то, что бы прям переписывают на Rust, но как минимум некоторые новые части пишутся на Rust. Если интересно, можете почитать про недавнюю уязвимость в curl, там в том числе и про переписывание на Rust.
SQLite
Не уверен, насколько они production ready, но на Rust пишут и базы данных, и key-value хранилища и прочие вещи, но сам язык в 3 раза моложе SQLite, так что трудно винить в этом язык.
Все же, "переписать все на Rust" это не то, что бы реальная цель или задача. Код, который полировали десятилетия никто просто так выкидывать не будет (уж точно не в первую очередь). Я поэтому и написал, что для меня у C и/или C++ ниша одна - легаси код, который слишком долго или дорого переписать.
в составе vcpkg около 2,5 тысяч библиотек
В Cargo.io на момент записи 141,981 пакетов. Это не честное сравнение, Rust поощряет разбиение пакетов на много мелких для переиспользования и ускорения компиляции, так что это не 141к обособленных пакетов, но все же.
Rust не гарантирует отсутствие гонок.
Rust следит за некоторыми популярными проблемами с многопоточностью (за счет владения переменной), но это не исключает гонки. Точно также можно в одну переменную писать и читать, пускай и через мьютекс.
Точно также можно в одну переменную писать и читать, пускай и через мьютекс.
Смотря что считать гонкой. Corrupt Shared State получить уже невозможно т.к. мютекс или другой какой атомик - и то хорошо. В плюсах это UB:
Critical race conditions cause invalid execution and software bugs. Critical race conditions often happen when the processes or threads depend on some shared state. Operations upon shared states are done in critical sections that must be mutually exclusive. Failure to obey this rule can corrupt the shared state.
A data race is a type of race condition. Data races are important parts of various formal memory models. The memory model defined in the C11 and C++11 standards specify that a C or C++ program containing a data race has undefined behavior.[3][4]
(c) https://en.wikipedia.org/wiki/Race_condition#In_software
А логические гонки - ну, это уже на совести погромиста, тут язык мешать не должен как мне кажется.
Не невозможно. Не забываем про unsafe в Rust. Сложнее - да. Но не невозможно.
Кроме того, встречаются либы, которые просто обертки над C-либами. Что тоже не спасет от Corrupt Shared State. Впрочем, это тоже можно отнести к unsafe.
А вот свой код, особенно в рамках safe Rust - да, спасает от подобного. И это очень и очень круто. Потому как дебажить подобное очень сложно.
Гонка данных и состояние гонки это две разные проблемы, они даже подклассами друг друга не являются. Цитата из The Rustonomicon:
Safe Rust guarantees an absence of data races, which are defined as:
- two or more threads concurrently accessing a location of memory
- one or more of them is a write
- one or more of them is unsynchronized
A data race has Undefined Behavior, and is therefore impossible to perform in Safe Rust. Data races are mostly prevented through Rust's ownership system: it's impossible to alias a mutable reference, so it's impossible to perform a data race. Interior mutability makes this more complicated, which is largely why we have the Send and Sync traits.
However Rust does not prevent general race conditions.
Он строго гарантирует отсутствие гонок данных, но не может дать гарантии отсутствия гонок условий.
Не забываем про unsafe в Rust. Для unsafe никаких подобных гарантий нет.
Для unsafe подобные гарантии обязан обеспечивать разработчик (т.е. ситуация не хуже, чем в других языках).
Да, не хуже, чем в других языках, ни капли не спорю.
Просто не надо забывать, что даже если напрямую не используешь unsafe - то твои зависмости вполне могут unsafe использовать.
А ещё бывают баги в компиляторе.
И кстати, если вас хоть немного заинтересовал Rust, то еще раз рекомендую лекции Алексея Кладова. Там всего 13 лекций по полтора часа, но этого с головой хватит, чтобы понять что, как, зачем и почему в Rust. Особенно по сравнению с моими мыслями, которые я кое-как собрал в статью.
Зачем прямо сразу переписывать существующие библиотеки на Раст, если можно сделать обёртку и подтянуть как есть? SQLite, Lua, curl спокойно подключаются, imgui и openimage переписаны "по мотивам". Qt только не будет, слишком другой менталитет.
Мне не нужен язык, мне нужно программу делать, а для этого нужны библиотеки, очень много библиотек,
По этой логике мы все до сих должны были бы писать на фортране. Однако же, не смотря на объём существующих библиотек, языки развиваются и появляются новые. Вполне естественно, что молодые языки беднее библиотеками, чем старые. Тем не менее новые языки набирают популярность и вытесняют старые, а библиотеки переписываются. Для джавы, вон, даже гит свой написали ("Отец, прости им, ибо они не ведают, что творят.") Учитывая, что в русте можно писать обёртки вокруг готовых библиотек, то ситуация даже проще.
Чем больше я читаю такие хвалебные оды расту и его преимуществам, тем сильнее у меня складывается ощущение, что фанаты раста, критикуя с и с++ за его возможность прострелить ногу, перестают понимать одну вещь - синтаксис с и с++ выстроен из неких математических абстракций вокруг архитектуры ЭВМ, он хоть и даёт возможность забыть про регистры, стек, прерывания и проч., но при этом он позволяет легко об этом вспомнить. Управление памятью, подход к разработке, определяемый компилятором - это всё наверное прекрасно, но кто уверен, что компилятор не ошибается? Ах,да, его пишут боги программирования, они никогда не ошибаются, ведь они пишут язык и компилятор, который никогда не ошибается. Возможно я сильно ошибаюсь, но разрабы, пишущие на расте, скорее всего не понимают, что там под капотом происходит, ведь "у нас самый надёжный супер пупер компилятор,который генерирует самый надёжный и безошибочный код". А потом окажется, через пять лет, что в компиляторе была дыра, которая генерировала код, в котором присутствовали сигнатуры, благодаря которым хакеры могли легко его реверсить и делать эксплойты. Да не, бред какой-то, это же самый надёжный и безопасный язык. Именно благодаря тому, что я на своих плюсах могу стрелять в ногу, и регулярно это делаю, я знаю как сделать так, чтобы мне никто другой не смог выстрелить в ногу. Ничто не совершенно, никто не совершенен.
Дыру в Rust-компиляторе найдут и исправят, фикс автоматически применится для всех Rust-программ, собранных новым компилятором.
Миллионы выходов за пределы массива, use-after-free и прочих UB в миллионах сишных программ не найдут и не исправят никогда.
могли легко его реверсить и делать эксплойты
Да вперед, большая часть кода, где "делают эксплойты" вообще в опенсорсе.
разрабы, пишущие на расте, скорее всего не понимают, что там под капотом происходит
Так в этом и плюс Rust,ты можешь позволить себе не знать, что там происходит (и использовать Rust как Python 4) и при этом писать быстрый и корректный код.
В safe Rust UB нет (точнее, если оно есть то это баг компилятора, который надо исправить), так что мне с практической точки зрения сказать нечего. Из моего теоретического опыта UB действительно очень плохо.
Насколько я понимаю, UB - это когда компилятор считает, что у него есть какие-то гарантии, которые в реальности не выполняются. С UB компилятор может сделать все что угодно, т.к. он будет оптимизировать код исходя из неверных предпосылок. Из-за этого компилятор может, например, просто скомпилировать программу, которая делает не то, что написано в коде или удалить кусок кода с UB, т.к. в правильной программе UB быть не может, а значит этот код недостижим.
UB одновременно зависит от всего вокруг (от кода вокруг, от компилятора, от версии компилятора, от багов в компиляторе, от фазы луны и т.д.), а в другой стороны, если в коде есть UB то эта программа - не программа. Компилятор имеет право сделать абсолютно что угодно со всем кодом т.к. код просто невалиден. Вот статья с примерами UB. Я после этого даже смотреть на языки с UB не могу, там надо прикладывать какие-то невероятные усилия для правильной работы конечного продукта.
Всё УБ от оптимизаций на предположениях. С отключением оптимизации будет выполняться то, что написано.
Всё УБ от оптимизаций на предположениях
Не готов ни подтвердить, ни опровергнуть, моих компетенций тут недостаточно. Но писать на C или C++ чтобы потом код без оптимизаций компилировать вряд ли кто-то будет. Проще сразу взять более медленный, но безопасный язык.
Ну вот я пишу без оптимизации на C89 для промавтоматики и мне важно чтобы выполнялось всё как написано в коде. Как раз и медленно и безопасно!
А зачем вообще выбран Си, если нужно "медленно и безопасно"? Там и кроме UB ногострелов достаточно же...
Потому, что правильная программа на языке С делает только то что ей говорят, и только тогда когда ей говорят. У нее например не запускется сборщик мусора в момент когда тебе нужно обработать критические данные и нужна максимальная производительность.
Написание программ на ассемблере например, это не просто возможность выстрелить себе в колено, это попытка не попасть себе в ногу при каждом шаге, но его продолжают использовать, пишут на нем критически важные участки кода, потому, что ни что не дает большего контроля над железом. А Си наверное самый близкий к железу высокоуровневый язык.
Я понимаю за что современные прикладные программисты не любят С, слишком сложно, слишком много вещей надо держать в голове и не забывать контролировать, это тяжело.
Это просто не их инструмент, оставьте его системщикам, они лучше знают что им надо. Язык Cи в системном программировании "заменяют" последние лет 25 кажется, а он жив до сих пор, по моему мнению только потому, что в этой области удобнее ничего не создали. Безопаснее возможно да, удобней нет.
Так вот у Раста и цель, чтобы сделать язык, на котором можно писать программы, которые делают только то, что им говорят, только ещё без (многих из) острых углов С.
Потому, что программа на языке С делает только то что ей говорят, и только тогда когда ей говорят.
Ага, и честно выходит за границу массива при ошибке в индексе. Ну прямо то что нужно в промавтоматике - молча перетирать соседние переменные при ошибках в программе.
У нее например не запускется сборщик мусора в момент когда тебе нужно обработать критические данные и нужна максимальная производительность.
Существуют куда более безопасные языки без сборщика мусора или с отключаемым сборщиком.
Это просто не их инструмент, оставьте его системщикам, они лучше знают что им надо.
Вот именно, пусть промавтоматчики и оставят этот язык системщикам, а сами перейдут на что-нибудь более безопасное. Скажем, Rust если нужна производительность, или на какой-нибудь Python если она не нужна. Да и вообще у них даже свои собственные языки программирования есть, хотя к ним у меня отдельные претензии.
Ага, и честно выходит за границу массива при ошибке в индексе. Ну прямо то что нужно в промавтоматике - молча перетирать соседние переменные при ошибках в программе.
Мне почему-то казалось, что язык не должен исполнять роль нянечки в детсаду - ходить за каждым ребенком с платочком и вытирать ему сопли.
Или цель разработки новых языков в том, чтобы всемерно снизить порог входа, чтобы любая кухарка могла управлять государством любой прослушавший вполуха какой-нибудь инфоцыганский курс, как можно быстрее стал "суперсеньором"?
Если в индексе ошибка - оно все равно выстрелит. Не здесь, так в другом месте. Не сейчас, так потом. А так оно просто маскируется языком и вроде как все в порядке.
А если говорить о "безопасности", то решается это на системном, а не языковом уровне - любой объект в системе (а переменная - это тоже объект). должен иметь "операционный дескриптор", который в т.ч. содержит его размер. И любое некорректное обращение будет вызывать системное исключение "выход за границу объекта". В любом языке.
Мне почему-то казалось, что язык не должен исполнять роль нянечки в
детсаду - ходить за каждым ребенком с платочком и вытирать ему сопли
Люди, даже имеющие огромный опыт, ошибаются. Особенно легко ошибиться при рефакторинге. Если какие-то свойства программы можно проверить автоматически - лучше это сделать автоматически, и чем раньше, тем меньше стоимость ошибки. Именно поэтому даже в традиционно динамических Python и JS (в виде TypeScript) добавляют аннотации типов. Именно поэтому в Расте существует safe подмножество языка, в котором можно спокойно мешать код и не бояться о тысячах способов получить UB. Именно поэтому индустрия стремится отойти от языков со слабой типизацией навроде Си.
Если в индексе ошибка - оно все равно выстрелит. Не здесь, так в другом
месте. Не сейчас, так потом. А так оно просто маскируется языком и вроде
как все в порядке.
Отличное описание Си, но что же хорошего в этой маскировке?
И любое некорректное обращение будет вызывать системное исключение "выход за границу объекта"
К сожалению, мы находимся не в идеальном мире, и самые популярные архитектуры не содержат необходимых механизмов для реализации этого. Кроме того, подобные проверки решают только ограниченное количество возможных проблем доступа к памяти.
Люди, даже имеющие огромный опыт, ошибаются. Особенно легко ошибиться при рефакторинге. Если какие-то свойства программы можно проверить автоматически - лучше это сделать автоматически, и чем раньше, тем меньше стоимость ошибки. Именно поэтому даже в традиционно динамических Python и JS (в виде TypeScript) добавляют аннотации типов. Именно поэтому в Расте существует safe подмножество языка, в котором можно спокойно мешать код и не бояться о тысячах способов получить UB. Именно поэтому индустрия стремится отойти от языков со слабой типизацией навроде Си.
Я полностью согласен. Но... Если вы начнете мешать код без риска получить UB, то рано-поздно вы все равно обманете компилятор и потом долго будет думать - почему же оно работает не так ка положено, хотя никаких ошибок тут нет ни при компиляции ни при выполнении?
Или нет таких трудностей, которые мы не смогли бы себе создать. За 30+ лет в разработке в этом убедился многократно.
Отличное описание Си, но что же хорошего в этой маскировке?
Ничего. Кроме того, что любой разработчик, который понимает как оно работает внутри, даже без отладчика вам сразу скажет в чем проблема. Просто "вполглаза" глянув на код.
А вот человек, который привык во всем полгаться на язык и компилятор - да. Будет долго чесать репу.
Спросите любого хирурга - что он предпочтет - скальпель, которым можно порезаться, или пластиковый ножичек из одноразового набора для пикника, который в этом плане абсолютно безопасен.
В целом я не поддерживаю что-то одно, но считаю что каждому овощу свой фрукт. И для каждой цели свой инструмент. И нет абсолютно лучшего и универсального на все случи жизни.
Но если вы взялись на инструмент - изучите все правила безопасной работы с ним и привыкнете их соблюдать. Или, если это непосильно, делайте то, что не требует такого сложного и опасного инструмента.
К сожалению, мы находимся не в идеальном мире, и самые популярные архитектуры не содержат необходимых механизмов для реализации этого. Кроме того, подобные проверки решают только ограниченное количество возможных проблем доступа к памяти.
Ну лично я нашел себе идеальный кусочек, где это есть. Может быть не в полном объеме, но в значительной степени оно так. :-)
Ничего. Кроме того, что любой разработчик, который понимает как оно работает внутри, даже без отладчика вам сразу скажет в чем проблема. Просто "вполглаза" глянув на код.
Это только для простых случаев, типа выхода за границы массивов. А если там какое-нибудь совсем не очевидное UB, то даже с отладчиком можно просидеть много часов. Например, потому что под отладчиком и в дебажном билде все отлично работает, а без отладчика и в релизом билде уже нет.
Дело не в нянечках и соплях, а в возможности сосредоточить внимание на действительно важных вещах.
А если говорить о "безопасности", то решается это на системном, а не языковом уровне - любой объект в системе (а переменная - это тоже объект). должен иметь "операционный дескриптор", который в т.ч. содержит его размер. И любое некорректное обращение будет вызывать системное исключение "выход за границу объекта". В любом языке.
И после этого вы ещё заявляете о вреде проверок границ, которые приходится отключать в тяжёлых расчётах?!..
Да, решить проблему на уровне платформы - идея хорошая. Только вот в чём проблема - на системном уровне этого не сделать, тут нужна аппаратная поддержка. И неслабая, поскольку хранение длины в указателе приводит минимум к удвоению его размера - а значит, для сохранения производительности нужно удваивать шину данных.
язык не должен исполнять роль нянечки в детсаду
Практика (которая критерий истины) показывает, что писать безопасно на C и/или C++ не может никто. Rust позволяет людям с меньшим опытом писать настолько же быстрые и гораздо более безопасный (уменьшение количества уязвимостей в 70 раз это слишком хорошо, чтобы от этого отказываться).
любой объект в системе должен иметь "операционный дескриптор"
Только систем таких нет. В Rust как раз по умолчанию все это и проверяется (как на этапе компиляции, так и в рантайме).
А потом говорят, что сообщество Rust токсичное. Да, токсичное тем, что признаёт человеческие слабости и пытается ради общего блага переложить заботу о технических тонкостях на машину. Не [только] для того, чтобы уменьшить порог входа, а для того, чтобы помочь и опытным разработчикам произвести больше полезной работы.
Вы всё ещё можете отключить "нянечку в детсаду", но только при этом вам придётся подумать -- а почему это мне приходится так делать? Обосновано ли это? Не подкладываю ли я себе свинью на будущее? А потом обклеить опасный участок яркой лентой и поставить табличку "осторожно, скользкий пол", чтобы в будущем было проще искать такие места.
А если говорить о "безопасности", то решается это на системном, а не языковом уровне - любой объект в системе (а переменная - это тоже объект). должен иметь "операционный дескриптор", который в т.ч. содержит его размер. И любое некорректное обращение будет вызывать системное исключение "выход за границу объекта". В любом языке.
Что характерно, защищённый режим x86 как раз про это. Но никто не заценил фишку, не воспользовался ей, не создал ни ЯП, ни ОС, которые бы использовали это в полной мере. А затем и Intel забросила и похоронила концепцию.
А до счастья оставался маленький шаг: сделать PDBR не частью CR3, а частью дескриптора сегмента, чтобы проблема фрагментации линейного адресного пространства перестала быть актуальной.
И была бы аппаратная защита от выхода за границы массивом и буферов. Buffer overflow attack не было бы как класса. Атак с шеллкодам не было бы как класса.
Я немного про другое.
Вот, например, получили вы некоторый указатель аргументов функции. И представьте, что у вас есть некое системное API которое позволяет получить информацию о том, на что этот указатель указывает - тип объекта, размер памяти, который он занимает... Т.е. к каждому объекту есть еще "операционный дескриптор" (operational descriptor, opdesc) по которому можно получить некоторую интересную информацию.
Единственное - чтобы это использовать, нужно я вно указать компилятору что требуется передача операционных дескрипторов в функцию. Для С/С++ это будет
int func (char *, int *, int, char *, ...); /* prototype */
#pragma descriptor (void func ("", void, void, ""))
В данном случае для аргументов char* внутри функции всегда можем посмотреть - а что на самом деле за ними кроется - какого размера буфер?
В других языках иначе - в некоторых просто достаточно модификатора OpDesc в прототипе функции.
Тогда это половинчатое решение какое-то...
Ваш #pragma descriptor требует пояснения: я не понял, почему в прототипе int (тип возврата), а в прагме void, что означают пара пустых строковых литералов?
И ещё: в вашей концепции запрашивать информацию о размере буфера это обязанность программиста или это неявно будет делать компилятор?
Мне почему-то казалось, что язык не должен исполнять роль нянечки в детсаду - ходить за каждым ребенком с платочком и вытирать ему сопли.
Вы теперь мой любимый комментатор на Хабре: ходите по всем статьям, где упомянут C/C++, и вбрасываете порцию токсичности. Через пару комментариев не забудете упомянуть, что вы консультант по разработке и должность придумали специально для вас. А также, что вы каждый день работаете с кодом, который не правился 10 лет (видимо, на него смотрите) и ещё пару раз оскорбите программистов, пишущих на чём-то, кроме вашего любимого языка.
Так и день пройдёт)
Где вы увидели токсичность в адрес С/С++?
Написано ровно то, что написано. И ничего более. Додумать можно все что угодно, но это уже из разряда "сама придумала - сама обиделась".
К С у меня вполне себе нежные чувства при всех его недостатках. Да, это "опасный" инструмент. Но это уже из серии про дурака и стеклянный йенг.
С С++ сложнее - мне он очень нравился на заре, когда был в варианте "ОО-расширения С". Сейчас, как мне кажется, было бы правильно полностью абстрагироваться от старого наследия и развивать его как несовместимый с С отдельный язык (каковым, по сути, он уже и является). Ну и не переусложнять, пытаясь втащить туда все подряд, а сосредоточится на устранении разного рода UB (раз уж так сейчас модно "безопасно" пользоваться).
Но в целом никаких предубеждений к нему нет. Язык как язык. Не хуже и не лучше (по совокупности характеристик) многих прочих в широком классе задач (но не любых задач).
Есть некоторая предвзятость в людям, которые начинают с пеной у рта доказывать что вот они нашли язык, который самый лучший и самый универсальный для всего на свете. Такого точно не бывает. Можно найти лучший инструмент для конкретной задачи, но не для всех сразу. Получится то ли нейрохирург с бензопилой, то ли лесоруб со скальпелем. Хотя в своих областях и скальпель и бензопила работают отлично.
Где вы увидели токсичность в адрес С/С++?
Вы невнимательно прочитали: я как раз написал, что ваша токсичность направлена на людей, не пишущих на вашем любимом языке. Можно вспомнить, как вы пренебрежительно относитесь к "перекладывателям джейсонов")
Есть некоторая предвзятость к людям
Именно.
начинают с пеной у рта доказывать
Никакой пены у рта у автора не замечаю. Человек делится своими впечатлениями, притом честно даёт представление о своих компетенциях, чтобы мы могли адекватно оценить подаваемый материал. Он же не пишет: "я 20 лет писал на C, но в конец задолбался и перехожу на Rust, так как в очередной раз вылетел за границы массива", верно?
А вот у вас, кажется, что-то капает. Возможно, кровожадная слюна: жаждете выпить из молодых специалистов всю радость от профессии.
А почему тогда не Pascal, Oberon или Ada?
B&R любит С. Да и всякие PC-based тоже. Но при наличии культуры кода, руководств, правильных ограничений и отсутствия всяких "смотри как я могу", получается легко читаемый структурированный код.
В таком случае вы ходите по минному полю. Эффекты от UB обычно проявляются при включенных оптимизациях, но язык и компиляторы не дают никаких гарантий, что при выключенных оптимизациях эффектов от UB не будет. Если вы действительно таким образом пишете для промавтоматики, то я переживаю за пользователей этой автоматики, очень надеюсь что там не что-то связанное ответственными и опасными применениями.
Оптимизации никак не помешают сначала удалить указатель, а потом его прочитать. И у вас он будет правильно читаться, а у клиента SEGFAULT. Классика же.
Всё УБ от оптимизаций на предположениях. С отключением оптимизации будет выполняться то, что написано.
Покажете параграф в стандарте языка, гарантирующий это?
А покажите параграф в стандарте где должно выполняться что-то иное в противовес к директивам программы?
А покажите параграф в стандарте где должно выполняться что-то иное в противовес к директивам программы?
Не "должно", а "может". Параграф 3.64:
Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).
То есть прямым текстом стандарт допускает что в случае undefined behaviour может быть совершенно unpredictable результат - в случае UB стандарт разрешает компилятору сгенерировать вообще все что угодно, даже то, чего нет в "директивах программы". Каких-либо исключений из этого правила в случае отключенных оптимизаций компилятора там нет, никаких гарантий не даётся.
А ещё есть замечательный параграф 4.1.2, где сказано
A conforming implementation executing a well-formed program shall produce the same observable behavior as one of the possible executions of the corresponding instance of the abstract machine with the same program and the same input. However, if any such execution contains an undefined operation, this document places no requirement on the implementation executing that program with that input (not even with regard to operations preceding the first undefined operation).
То есть стандарт явно допускает что программа с UB может творить любую хрень (в том числе и то, чего нет в "директивах программы") даже ещё до выполнения того места, где было UB.
С UB встречаешься обычно при смене компилятора / платформы / архитектуры.
Если одним компилятором компилировать под тот Win x64, то шансы встретить UB пускай и есть, но они практически все всплывают еще на этапе тестирования.
Пускай опыта с C/C++ у меня совсем немного, но с UB встретился плотно один раз - когда watch дебаггера и реальное исполнение кода давали разные результаты. В итоге я очень долго отлаживал этот код, не понимая в чем проблема.
Таки Rust лучше C/C++?
Слушайте, ситуация с Rust напоминает ситуацию с ЛГБТ-повесточкой.
Я совершенно ничего не имею против тех, кто пишет быстрый, надежный и безопасный код на RUST. Молодцы. Но я просто бешусь, когда они начинают рассказывать мне как я должен писать код. Вот и все.
Ситуация действительно как с лгбт-повесточкой, а именно: в оьоих случаях противники путают информацию с агитацией. Каким образом статья на хабре может заставить вас писать иначе? Это же просто информация.
Ну да. Я ведь ровно о том же.
Вообще-то я комментировал не статью. У меня к ней нет никаких претензий. Я отвечал на откровенно провокационный комментарий. И даже процитировал ключевую его часть.
P.S.
И еще - я вместо "противники", писал "молодцы". Что уже само по себе характеризует отношение а оппоненту. Разве нет?
Аргумент одинаковый — если у вас достаточно неокрепший(детский/подростковый/студенческий в случае Rust) ум, то вас легко направить по тому или иному пути. Если вы 40-летний сеньор, то радужный раст вам нестрашон:)
Много с чем ситуацию напоминает - "холивар" это называется. Было всегда и будет ещё.
Ну да. Лучший инструмент - это тот, которым ты сегодня можешь делать продукт.
А те, кто только начинает вольны выбрать любой из представленных инструментов. В конце-концов всегда "фотографирует фотограф, а не фотоаппарат". И вообще, "Пусть расцветают сто цветов, пусть соперничают сто школ" (с)
Rust в этом плане просто быстрее внедряет желаемые вещи, пока С++ комитет решает завести новый функционал в стандарт или оставить до новой версии на подумать - в расте оно уже работает в достаточно эргономичном виде. Для примера взгляните на ту же библиотеку ренжей и вью - сколько лет до них добирались и до сих пор нет полного покрытия у всех компиляторов, в то время как в Rust часть синтаксиса языка. Причины понятны, но работать от этого легче не становится. Нормальные модули ждём по сей день.
Rust Embed вполне активен насколько мне известен и опять же эргономика и инструментарий этой экосистемы в среднем лучше чем альтернативы для C++, хотя эквивалентный код вполне можно было написать и на C++. Т.к. работа с платами в любом случае будет сопряжена с манипуляциеми с голой памятью, то на определённых уровнях так или иначе всплывёт unsafe и возможность скрафтить UB, так что с плюсами в этом плане различий меньше. Оно будет просто удобнее в среднем.
наверняка на этот счет есть линтер, но такой код просто не должен компилироваться. А про очевидность поведения zero value для разных типов мне даже говорить не хочется
Вот тут есть чоткое непонимание ситуации. Цель в том, что новое поле всегда может найтись и оно будет нулевым. Приползёт от старого сервиса. Очнётся зомби с кодом недельной давности. Придётся откатить один из сервисов. Надо прочитать файл который создали год назад. И так далее. То есть "новое поле == нечто нулевое" это норма с которой должен работать любой код. Нельзя "не скомпилировать", потому что это нормальное поведение рантайма.
Если мы поддерживаем старое API, то можем сделать это поле опциональным и обрабатывать надлежающим способом. А вот уже другие ситуации, по моему мнению, стоит рассматривать отдельно, иначе можно получить неприятные ошибки.
И не просто ошибки. Я помню истоии об уязвимостях возникшие именно так. И эксплутируемые именно на основе такого поведения "нулевого поля". По-моему именно из-за этого несмотря на существование "нулевого" поля в "google protobuf", они также ввели и опциональные , и значение по умолчанию, которое "на проводе" кодируется тем же нулем. Т.е. ноль в данных не есть ноль на выходе.
Как раз подход с молчаливой инициализацией нулями порождает ошибки, возможности для которых создатели Раста постарались минимизировать. При молчаливой инициализации компилятор не может определить, не забыл ли программист учесть то самое "легаси", про которое Вы говорите.
В Расте же стараются сделать так, чтобы программисту для учёта подобных случаев приходилось явным образом что-то добавить в код, и компилятор, соответственно, может распознать, забыл ли программист учесть то "легаси" или нет.
Приведённые Вами ситуации не означают, что нужна автоматическая инициализация нулями. Они означают, что инициализатор (конструктор), который создаёт структуру из соответствующего источника, должен явным образом обработать эту ситуацию. Если при отсутствии значения соответствующее поле должно инициализироваться нулём - ок, пусть так, но это должно быть явным образом осознанно сделано программистом, а не произойти автоматически. И именно про это говорит автор статьи.
Кроме того, что уже сказано выше про неинициализированные поля мне добавить нечего. Добавить я могу только то, что, по моему мнению, zero values в Golang это ошибка сама по себе. Это заставляет все типы иметь какое-то значение по умолчанию, хотя для типа может просто не быть такого значения.
Каналы хороший пример такого, в nil канале нет никакого смысла, у него нет и не может быть какого-то логичного поведения. Из-за этого теперь у nil канала есть очень странное поведение - создание дедлоков. Зачем вообще иметь возможность создать, по сути, невалидный объект, который при любом обращении вызывает баг?
Или разница в поведении слайсов и мап. Nil слайс на практике от слайса нулевой длины ничего не отличается. Nil мапа же почему-то не может при записи аллоцировать память и теперь у такой мапы есть 2 возможных использования: 1) узнать, что ключа (любого) в этой мапе нет; 2) получить панику при попытке ключ записать. И такой потрясающий объект можно вернуть из функции ничего не подозревающему пользователю, вот он то обрадуется.
Не касательно содержимого статьи, а в целом. Надо понимать что удобство и популярность языка ещё очень сильно зависят от документации и комьюнити. И, может быть субъективно, но у раста очень токсичное комьюнити.
Это довольно неожиданное утверждение. :) Очень грустно его видеть. Обычно напротив говорят, что растовское комьюнити - дружелюбное, гораздо дружелюбнее среднего по индустрии.
А что заставляет Вас считать обратное? Вы лично столкнулись с неадекватной агрессией, когда пришли за советом? Не расскажете подробнее?
Я потому и написал, что это субъективное суждение (меня и некоторых моих друзей). Вполне допускаю что это ошибка выжившего. Я не часто по работе пересекался с Rust программистами, но был один совместный проект в пару лет длиной. Не могу чётко сформулировать свои претензии (оценка токсичности вещь относительная), но большая часть rust программистов с которыми я сталкивался оказывались не в меру высокомерными и слишком большими адептами из разряда «кроме раста другие языки не нужны», «если всё переписать на раст, то мир станет лучше», «вы тупые потому что вы не понимаете прелесть раста», «ваш с++ говно» и так далее, в таком духе.
Вот в чем точно "недостаток" (сами решайте, насколько это действительно недостаток) так это в том, что он заставляет очень критически относится к другим языкам.
После Rust я просто не смог смотреть на Golang, там слишком много вещей, которые должны были бы быть лучше. На C++ я тем более не посмотрю (о чем и в статье написал), для меня у него нет ни одного преимущества перед Rust и огромный товарный состав недостатков.
И да, мир действительно стал бы лучше, если бы все было переписано на Rust, может наконец-то избавились бы от проблем "быстрых" языков и от медлительности всех остальных.
Я вот наоборот, с Golang работаю после Rust (правда c Golang познакомился задолго до Rust).
И с одной стороны - действительно, Rust был гораздо более выразительным и первое время было сложно. А с другой - читать/изучать код стало гораздо легче.
Так что не скажу, что после Rust на другие языки смотреть невозможно. Вопрос привычки.
P.S. подобный эффект есть и если после Golang сесть за Java. Java кажется крайне многословной.
У всех свои приоритеты, мне после Rust и про Python сильно долго думать не хочется, в то тоска одолевает. Я понял, что Golang мне абсолютно не нравится когда проходил A Tour of Go и за сколько там упражнений нашел 4 возможности отстрелить себе лишнюю конечность:
возможность читать слайс после длины;
nil map, которая не позволяет делать с собой ничего полезного (хотя nil слайс на практике идентичен слайсу нулевой длины);
поведение nil и закрытых каналов;
возможность переопределять ключевые слова (знаменитое
#define true (rand() > 10) //Happy debugging suckers
).
Забыли ещё нетривиальное взаимодействие между append и изменением элементов по индексу
возможность читать слайс после длины;
Можете пояснить, о чём тут речь? Вроде же, если просто индекс вылезает за границы слайса - мы сразу получаем панику, как и в Rust?
возможность переопределять ключевые слова
Опять же, это, видимо, какой-то такой ногострел, с которым я пока не сталкивался, поясните?
Счастливый вы человек, раз не знаете. Скорее всего после следующего кода настроение у вас ухудшится, так что готовьтесь.
Чтение слайса после длины
package main
import "fmt"
func main() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)
// Slice the slice to give it zero length.
s= s[:0]
printSlice(s)
// Extend its length.
s = s[:6]
printSlice(s)
s= append(s, 7)
printSlice(s)
// Works just fine
d := s[0:cap(s)][11]
fmt.Printf("len=%d cap=%d d=%v\n", len(s), cap(s), d)
// Obviously this panics
g := s[11]
fmt.Println(g)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s[0:cap(s)])
}
С помощью такой техники можно создавать очень веселые гайзенбаги
package main
import (
"fmt"
"math/rand/v2"
)
type SlidingWindow struct {
s []int
offset int
size int
}
func (w *SlidingWindow) next() []int {
if w.offset > (len(w.s) - w.size + 1) {
return nil
}
t := w.s[w.offset : w.offset+w.size]
w.offset += 1
return t
}
func sliding_window(s []int, offset int, size int) []int {
return s[offset : offset+size]
}
func main() {
s2 := []int{1, 2, 3, 4, 5, 6}
if rand.IntN(10) > 5 {
s2 = append(s2, 7) // panics without append
}
sw := SlidingWindow{s2, 0, 3}
for w := sw.next(); w != nil; w = sw.next() {
fmt.Println(w)
}
}
Переопределение ключевых слов
package main
import (
"fmt"
"math/rand/v2"
)
func really_big_func() (a, b bool) {
// a lot of code
// Happy debugging, suckers
true := rand.IntN(10) > 5
false := rand.IntN(10) > 3
fmt.Println(true)
fmt.Println(false)
// a lot of code
return true, false
}
func main() {
fmt.Println(really_big_func())
}
Погодите, в go операция вырезания куска массива-слайса может этот слайс расширить?! И это не баг, а документированное поведение?
Да какого хрена?!
Кстати, вот пример короче и показательнее
package main
import "fmt"
func main() {
s := []int{1, 2, 3, 4, 5, 6}
fmt.Printf("%v\n", s) // [1 2 3 4 5 6]
s = s[1:4]
fmt.Printf("%v\n", s) // [2 3 4]
s = s[1:4]
fmt.Printf("%v\n", s) // [3 4 5]
}
Погодите, в go операция вырезания куска массива-слайса может этот слайс расширить?! И это не баг, а документированное поведение?
Не просто документированное поведение, оно прямо в Golang tour описано. И не в том смысле, что не делайте так никогда, а как просто еще одна возможность языка.
...мда, спасибо, первое действительно проклято. Второе-то ладно, просто кто-то явно забыл про то, как JavaScript прошёлся по граблям с переопределением undefined...
В этом моя проблема с Golang, они там очень много чего "забыли","не знали" или "проигнорировали". Не то, чтобы я считал себя умнее создателей Golang, они явно делали язык под свои конкретные нужды, но когда рандом с улицы (я) может за 5 минут найти дыру в языке, который только начал изучать, то это как-то не очень.
Я бы ещё добавил любовь к "программированию в комментариях". Вот уж просто минное поле.
Вот за эти высказывания вас и не любят. Скромнее надо быть :)
На несколько сообщений выше сообщество раста уже с геями сравнили за то, что "посмели" рассказывать о преимуществах своего языка. Просто для некоторых людей "токсичное" это обозначает "всё, с чем я не согласен, включая то, что я считаю навязыванием хорошего поведения".
Тут скорее зависит не от факта согласия-несогласия, а от того, в какой форме он подается
А с формой-то что не так?
Вот взять обсуждаемый пост. Вроде нормально написан же, но в комментариях всё равно упомянули токсичное сообщество. Получается что ни напиши про Rust - обвинений в токсичности не избежать. Точно ли именно Rust тут токсичен?
Ну так оно действительно токсичное. Даже автор данного поста допускает заявления типа "мир действительно стал бы лучше, если бы все было переписано на Rust".
Столь радикальные заявления это какой то детский максимализм или рвение неофитов, и у кого то вызывает лишь улыбку, а у кого то сомнения в адекватности этих коллег...
Будет ли специалист по нейросетям, разрабатывать нейросети на rust? Сильно сомневаюсь. В данной задаче язык при помощи которого загружаются данные и выводятся результаты вообще не критичен и тот же Python подойдет лучше, просто за счет того его проще изучить.
Можно ли парсер логов сделать на расте? Да. Он будет быстрым? Да. Будет ли он столь же понятен изящен и лаконичен как на питоне или прости господи perl. Полагаю нет.
Можно ли обработчик прерывания написать на раст? Можно (спасибо Филиппу Опперману). Будет ли он лучше в этом обработчика на С или ASM? Нет.
Думаю смысл вы уловили, есть около 7000 языков программирования каждый создавался для своей области применения, у каждого свои достоинства и свои недостатки, а заявления типа "твой любимый язык программирования отстой, а вот в раст..." ну выше уже вроде написали, что о таких персонажах думают.
Будет ли специалист по нейросетям, разрабатывать нейросети на rust?
Работаем потихоньку, вот примеры от самых хайповых компаний индустрии: https://github.com/openai/tiktoken, https://github.com/huggingface/candle, и в целом. Не смотря на все заявления "да питон там только клей для сишных библиотек, он ни на что не влияет" именно питон часто становится ботлнеком, даже при запуске тяжёлых нейронок, вот так да.
Можно ли парсер логов сделать на расте? Да. Он будет быстрым? Да. Будет ли он столь же понятен изящен и лаконичен как на питоне или прости господи perl. Полагаю нет.
Думаю, вы сильно удивитесь, но Rust - это один из лучших языков для написания парсеров. Наличие нормальных енумов и довольно продвинутого pattern matching'а позволяет как очень легко описывать токены, так и легко разделять логику парсера под разные токены.
Серьёзно? Это и правда очень интересное утверждение. Приведите, пожалуйста, примеры конкретные в сравнении с аналогичными ситуациями в комьюнити других языков? Мне наоборот показалось там все такие няши, что аж не верится и Линуса на них нет)
Не могу говорить за все Rust сообщество, люди там разные и бывает всякое, я точно помню как минимум несколько довольно крупных скандала в Rust комьюнити, но с другой стороны я не знаю ни одного сообщества, где не было бы ни одного скандала вообще.
Обычно, если нужна какая-то помощь, то тут и помогут, и объяснят разные варианты с их плюсами и минусами или почему это плохая идея. В обычном общении я не видел примеров токсичного поведения.
Абсолютно токсичное по отношению к остальным, сами они этого не видят. Невероятно много контента от различных авторов и комментаторов с +- одним тезисов "А Rust лучше...". Язык может и не плохой и место для него найдется. Но ребята напоминают секту, а цель этой секты весь мир на Rust переписать. А для меня например, т.к я не пишу на rust, некоторые примеры из этой статьи абсолютно не читабельны, хоть и местами смахивает на кресты. Охотно верю, что проблема в отсутствии привычки и наметанного глаза к такому синтаксису. Но многие другие языки мне удавалось читать и понимать без знаний. Например Go отлично читается любым разработчиком знакомым с С подобным языком, почти любая конструкция будет очевидной.
То, что для одного "токсичность", для другого "энтузиазм". Продвигать и агитировать за хорошие вещи -- это не токсичность, а желание поделиться.
Тыц, тыц, плюс скрытый (модератором?) комментарий того же гражданина к посту, который мы сейчас обсуждаем - это не "энтузиазм". Трудно сказать, чем конкретно вызвано такое поведение, возможно, это что-то медицинское, но я, когда встречаю очередную мульку про "нетоксичный энтузиазм растаманов", всегда вспоминаю этого гражданина. Как говорится, стоит однажды вы@бать овцу...
Вот, кстати, автор - тоже в своем роде жертва пропаганды. Вот он пишет "На C++ я тем более не посмотрю (о чем и в статье написал), для меня у него нет ни одного преимущества перед Rust и огромный товарный состав недостатков", то есть человек взял и сам себя ограничил, добровольно надел себе шоры. Языка человек не знает (даже C он так и не осилил), но почему-то он уверен, что "у него нет ни одного преимущества перед Rust". Почему? Потому, что ему так кто-то сказал. Воля ваша, но это нездоровый подход.
По высказываниям одного человека вы судите обо всём комьюнити? Тем более, что с 22 года Роман уже не является активным участником ru-rust. По-моему вы просто раздуваете на ровном месте.
По высказываниям одного человека вы судите обо всём комьюнити?
Да если б одного... Просто этот в очередной раз засветился в каментах к статье, которую я читаю, дал повод о себе вспомнить.
Тем более, что с 22 года Роман уже не является активным участником ru-rust.
Извините, не слежу, кто там является активным участником чего. Но если не является - тем лучше для коммьюнити. От таких нужно избавляться. Как говорится, лучше поздно, чем никогда.
По-моему вы просто раздуваете на ровном месте.
А по-моему не на ровном месте. Let's agree to disagree.
Странно, что в качестве примера токсичности растоманов вы выбрали два случая восхваления С++...
В качестве примера токсичности я выбрал два случая (на самом деле три), когда пациент просто приходит и начинает поливать клоунами, долбо@бами, и.т.д.
восхваления С++
И что? Типа раст можно "восхвалять", а C++ - нет? Вот за это вас и не любят (С) :)
Я знаю, что случая три, просто плюсы он "защищал" в двух.
И что? Типа раст можно "восхвалять", а C++ - нет?
Я такого не говорил. Но считать агрессивного фанатика С++ примером токсичности сообщества Rust как-то странно.
Вы явно что-то перепутали насчёт "агрессивного фанатика C++" :) Ознакомьтесь с профилем пациента получше.
Какая разница что у него в профиле? Примеры "токсичности сообщества Rust" менее странными от этого не станут.
Языка человек не знает (даже C он так и не осилил), но почему-то он уверен, что "у него нет ни одного преимущества перед Rust". Почему? Потому, что ему так кто-то сказал. Воля ваша, но это нездоровый подход.
А еще мне в школе "кто-то сказал" про правила русского языка, математику с геометрией (доказательства теорем, конечно, тоже давали, но что-то мне подсказывает, что никто из учеников не заметил бы ошибки в доказательстве), физику, химию и много чего еще. Жизнь у меня одна и проверить все самостоятельно невозможно. У C есть объективные проблемы, отрицать этот факт бессмысленно.
Мне бы хотелось больше вопросов по существу вопроса, а не по форме. Если я где-то не прав, вы не стесняйтесь, напишите. Если у вас будут хорошие аргументы я вам обещаю, что изменю свое отношение к C и C++.
Вопрос у меня такой, вот вы обвиняете Rust сообщество в токсичности, но тут же сами же в тексте используете агрессивные обороты типа "мулька", "что-то медицинское" или "вы@бать овцу".
Нет ли в этом противоречия?
А у меня другой вопрос - даже если предположить(!), что вы правы, и обороты "мулька" и т.п. являются, по вашему мнению, "агрессивными" (хотя по моему мнению это сильно напоминает попытку докопаться до столба), как это оправдывает поведение пациента по вышеприведенным ссылкам? :)
Мне кажется, перебор с символами пунктуации в синтаксисе языка: .|&*::-><(),_>>()?;
Это в коде еще lifetime-ов не было, количество знаков пунктуации удвоилось бы.
Существенная доля этих символов - желание сохранить определённую преемственность от C/C++, чтобы облегчить мало знакомым с Растом людям понимание кода.
Но важнее, пожалуй, понять какие есть альтернативы? Использование ключевых слов удлинило бы программу, сделав её набор медленнее, а текст менее наглядным. Вот взять тот же самый "?" - это же синтаксический сахар, который заменяет достаточно длинный код, делающий корректную обработку ошибок существенно менее напряжной!
От круглых скобок в if'е избавились - в отличие от многих языков программирования, они в Расте не обязательны (т.е. "if x > 0", а не "if ( x > 0 )" ).
От фигурных скобок можно было бы избавиться методом Питона.
От круглых скобок в функциях одного аргумента теоретически можно попробовать избавиться, но если аргументов более одного?
Мне очень интересно было бы увидеть подходы, за счёт которых можно было бы избавиться от лишних символов, одновременно увеличив выразительность кода!
Haskell =)
Использование ключевых слов удлинило бы программу, сделав её набор медленнее
Я очень надеюсь, что вы с пользой потратили эти сэкономленные 100 миллисекунд?
Мне в этом смысле нравится Питон. Код читается почти как просто английский текст.
Меня не особо волнует синтаксис как таковой, не Brainfuck и ладно. К любому синтаксису привыкаешь за 2 недели. Мне гораздо важнее то, что с помощью этого синтаксиса можно сделать.
Вам кажется
Появилось впечатление, что Rust сравнивался в 1ю очередь с Python. И после Python он оставил приятные впечатления.
В целом я могу согласиться, что Rust оставляет приятные впечатления. Своим синтаксисом, своими возможностями, своими абстракциями... Но это справедливо для небольших проектов. Для большого я бы его не брал. На Rust очень сложно писать - постоянно приходится бороться с компилятором. Но читать готовый код - еще сложнее. Макросы, сложный код, не всегда можно найти реализацию конкретного метода. Далеко не все библиотеки хорошего качества, что усложняет работу и так с непростым инструментом.
Лично я для большого проекта брал бы язык с более очевидным синтаксисом (и пускай с не такими чистыми абстракциями). Например, тот же Golang.
P.S. Да, статью "что не так с Golang" я бы почитал.
Но это справедливо для небольших проектов.
Как раз наоборот, Rust в полной мере раскрывается в больших, сложных или долгих проектах! Как раз основная мысль статьи в том, что Rust намного проще рефакторить, что наиболее важно как раз в больших проектах. В Rust компилятор не враг, чтобы с ним бороться, это лучший друг, которых покажет на ошибку на самом раннем этапе разработки.
К сожалению, у меня нет личного опыта подобных проектов в Rust. Мое мнение о Rust основано на опыте больших проектов в других языков, но о больших проектах говорят в Fast Development In Rust, Part One, Beyond Safety and Speed: How Rust Fuels Team Productivity, Grading on a Curve: How Rust can Facilitate New Contributors while Decreasing Vulnerabilities и нескольких реддит постах из начала статьи, так что мне кажется, что моя экстраполяция вполне валидна.
Лично я для большого проекта брал бы язык с более очевидным синтаксисом
Выше я уже отвечал, для меня синтаксис не играет большой роли. Rust позволяет мне выражать мои идеи в коде без необходимости неделю думать о UB, том, что надо создать тип Nothing чтобы отделять разные типы пустых значений и т.д.
Да, статью "что не так с Golang" я бы почитал.
Это скорее шутка, у меня, все же, слишком мало опыта в Golang, буквально 2 недели попытки его выучить и пара месяцев обсуждений с друзьями "как так оказалось, что в Golang все настолько плохо". Просто когда в официальном курсе на официальном сайте Golang я умудрился задуматься "хм, кажется это какой-то бред" (и при этом я оказывался прав) раза 4.
Но уже много кто написал такие статьи, я рекомендую I want off Mr. Golang's Wild Ride, Lies we tell ourselves to keep using Golang, They're called Slices because they have Sharp Edges: Even More Go Pitfalls, Golang is not a good language.
Для примера добавлю сюда примеры кода, который вызывает у меня вопросы в духе "а хоть кто-то при разработке над чем-то кроме GC и async рантайма думал вообще?":
Заголовок спойлера
package main
import "fmt"
func main() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)
// Slice the slice to give it zero length.
s= s[:0]
printSlice(s)
// Extend its length.
s = s[:6]
printSlice(s)
s= append(s, 7)
printSlice(s)
// Works just fine
d := s[0:cap(s)][11]
fmt.Printf("len=%d cap=%d d=%v\n", len(s), cap(s), d)
// Obviously this panics
g := s[11]
fmt.Println(g)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s[0:cap(s)])
}
package main
import (
"fmt"
"math/rand/v2"
)
type SlidingWindow struct {
s []int
offset int
size int
}
func (w *SlidingWindow) next() []int {
if w.offset > (len(w.s) - w.size + 1) {
return nil
}
t := w.s[w.offset : w.offset+w.size]
w.offset += 1
return t
}
func sliding_window(s []int, offset int, size int) []int {
return s[offset : offset+size]
}
func main() {
s2 := []int{1, 2, 3, 4, 5, 6}
if rand.IntN(10) > 5 {
s2 = append(s2, 7) // panics without append
}
sw := SlidingWindow{s2, 0, 3}
for w := sw.next(); w != nil; w = sw.next() {
fmt.Println(w)
}
}
package main
import (
"fmt"
"math/rand/v2"
)
func really_big_func() (a, b bool) {
// a lot of code
// Happy debugging, suckers
true := rand.IntN(10) > 5
false := rand.IntN(10) > 3
fmt.Println(true)
fmt.Println(false)
// a lot of code
return true, false
}
func main() {
fmt.Println(really_big_func())
}
package main
type Container struct {
a int // old field
Items map[string]int32 // new field
}
func (c *Container) Insert(key string, value int32) {
c.Items[key] = value
}
func main() {
c := Container{a: 2}
c.Insert("number", 32)
}
Я наоборот слышал, что рефакторить сложно Rust. Особенно если где какой тип поменять надо (или его обертку). Но тут лично за себя отвечать не буду - лично не участвовал в больших рефакторингах c Rust (хотя и очень косвенно наблюдал, и по сложившемуся впечатлению - это было "больно").
Но должен сказать, что рефакторинг - в любом языке не самая простая задача. Хотя он может быть достаточно безболезненен, если код хорошо покрыт тестами.
В Rust компилятор может и друг, но далеко не всегда можно понять "а что же тебе не хватило". И в таких ситуациях лучший подход - запустить пустой проект и там последовательно воспроизвести работающий код, чтобы понять "а почему это работает".
Вопрос с большим проектом не в синтаксисе, а в читаемости кода - чуть ниже расписал. UB и Nothing - не такие уж и большие проблемы на практике, я бы сказал (уточню, что с C/C++ я очень мало работал).
"хм, кажется это какой-то бред" у меня возникал и при чтении Rust book. Но уже не вспомню толком на какие моменты.
Тип менять больно в любом языке, в котором есть типы. Rust тут, напротив, хорош возможностью заранее "подстелить соломку" в виде псевдонима для типа (type alias) - в таких языках как C# даже это невозможно (и ничего, как-то рефакторим код).
Честно скажу, этот аргумент именно что слышал в каком-то обсуждении, по существу, из своего опыта, не могу ни подтвердить, ни опровергнуть.
P.S. А рефакторить тип в языках, где нет типов - это еще веселее )))
P.P.S. Да, я знаю что "нет типов" говорить некорректно. Динамическая типизация не исключает типы.
Я бы даже сказал, что лучше не лениться и для такого использовать паттерн New Type. Это позволяет гарантировать, что невозможно вместо старого типа использовать новый. Да, иногда приходится реализовывать базовые операции над типами заново, но гарантии того стоят.
У меня в моем трассировщике как раз есть такой пример: в оригинальной статье на C++ использовались как раз алиасы для color, vec3 и point3. Я не поддаваться на эту легкость и сделал три независимых типа (заодно узнал, как писать свой derive чтобы не копипастить базовые операции над типами).
Особенно если где какой тип поменять надо
Как раз пример из статьи (где я менял t64 на свою обертку над u8), как мне кажется, должен показать, что это как раз очень просто. Компилятор сразу выдаст все места, где надо поменять тип и его невозможно забыть его поменять - программа просто не скомпилируется. Для Rust компилятор - это как набор встроенных тестов, которые гарантируют, что типы везде сошлись. Конечно, тесты на логику работы лишними точно не будут, но это уже отрезает огромную пачку возможных проблем.
Но это справедливо для небольших проектов. Для большого я бы его не брал.
Вот это высказывание меня тоже несколько удивило... По-моему, Rust как раз в первую очередь предназначен для больших проектов, в нём значительная часть фич именно на большие проекты заточены. "Борьба с компилятором" (которая с накоплением опыта исчезает, кстати), в частности, как раз помогает получить поддерживаемый код, который не "развалится" при росте кодовой базы.
Моя основная претензия к Rust - его сложно читать. Сложно изучать код библиотек, особенно если библиотека не лучшего качества. Сложно погружаться "а что там внутри происходит" при отладке, особенно если там - макрос на макросе и макросом погоняет.
Именно этот фактор ограничивает, на мой взгляд, применение Rust в больших проектах.
"Борьба с компилятором" - да, со временем становится легче, не спорю. Особенно если к одному проекту более-менее привык и уже знаешь, как с ним работать. Но не могу сказать, что этот фактор пропадает совсем. И я понимаю, что "борьба с компилятором" vs "борьба с плавающими багами" - уж лучше компилятор помучить.
Про сложность ревью кода в Beyond Safety and Speed: How Rust Fuels Team Productivity говорил Lars Bergstrom. По внутренним опросам в Google android, 50% опрошенных сказали, что код на Rust проще проверять (возможно, что это по сравнению с C++ и тогда это не то, чтобы сильно впечатляюще). Так что это дело просто привычки, вместо поиска скрытого UB можно сконцентрироваться на других вещах.
Почему мне не сложно читать код библиотек на Rust? Я профессионально программирую на Rust уже более 5 лет. Но даже в первый год его изучения я прочитал СТОЛЬКО чужого кода в библиотеках, сколько не читал за предыдущие 10 лет. Так что не спешите обобщать свои выводы на всех.
Не знаю. Может попадалось слишком много библиотек с обилием макросов и кодогенерации. Так-то простой код Rust вполне читаем, вопросов нет.
Может не всегда понятно как именно он работает в деталях (с учетом трейтов и прочей магии шаблонов Rust), но что именно в нем происходит - вполне понятно.
Я вообще не программировал на Rust всерьёз, но мне тоже не составляет труда прочитать код на этом языке.
Мне кажется, борьба с компилятором – это хорошая возможность избежать проблем в проде. В основном, это ошибки про заимствования.
Я в основном пишу сейчас на Go, но с некоторыми вещами все еще не свыкся (внимание, дальше идет только очень субъективное ощущение):
После Rust - это наличие нулевых указателей. Так что это с большой долей вероятностью вылетит в рантайме и в проде.
Нет разделения mutable/unmutable указателей.
defer решает часть проблем, но не снимает их. Например, легко представить ситуацию, когда взял lock но забыл прописать defer на освобождение.
Лбв к скр им п.
Система импортов кажется топорной, не знаю как лучше объяснить.
Использование первого символа названия для определения публичности.
Обработка ошибок очень топорная и ограниченная.
Да, я тоже с Golang работаю после Rust (правда c Golang познакомился задолго до Rust). И со всеми пунктами согласен.
Но с опытом пришло понимание, что "чем проще - тем лучше". И Golang - это именно что простой язык. Он далеко не идеальный, но за счет простоты - я могу ему простить это.
P.S. сильно веселее было раньше, когда у Golang не было еще и менеджера зависимостей )
Система импортов кажется топорной, не знаю как лучше объяснить.
А как это проявляется?
У вас больше вопрос к разработке и опыту с ide, которые подерживают rust все лучше с каждым годом, но все еще не очень по сравнению с Java/Kotlin/C#. JetBrains уже отдельную ide для rust выкатили RustRover но еще в альфа релизе. А rust компилятор выводит довольно хорошие подсказки для новичков. Тем не менее после полугода работы с языком понимаешь сам без помощи компилятора, когда код собирется и как писать нужно т.е становишься продуктивнее и в плане чтения кода тоже. Библиотеки конечно бывают разные, ваша правда) Но потенциал развития довольно большой. Все частоиспользуемые либы вроде сериализаций(serde), веб фреймворков уже довольно хороши. Касательно больших проектов. Большая часть бэкенд сервисов одной крупной криптобиржи написано на rust https://blog.kraken.com/product/engineering/oxidizing-kraken-improving-kraken-infrastructure-using-rust и воочию выглядели неплохо.
Не знаю на счет RustRover, но раньше их плагин Rust основывался на LSP. И с момента прихода rust-analyzer взамен RLS стало намного лучше.
Но большой разницы с той же VS Code я не увидел - LSP и там и там.
Да, стандартная библиотека, крупные и популярные либы - выглядят очень неплохо (подозреваю, это и было фактором, определившим их популярность).
Опять же, подсказки, в большинстве, своем очень хорошие, спору нет. Но не всегда из них можно понять что именно не так и как это исправить.
После полугода с языком - может некоторое число проблем с компилятором и уходит, но не исчезает полностью. И заранее предсказывать мне так и не удавалось. Возможно тут сыграл фактор, что я работал над множеством небольших и разноплановых проектов (в большинстве своем), а не над одним большим. И к каждому проекту привыкать надо отдельно - а общего навыка под множество либ и их особенностей так и не выработалось за это время.
Большой проект из множества микросервисов - это не тоже самое, один большой монолит )
С микросервисной архитектурой Rust прекрасно справляется, спору нет. Микросервисы писать на Rust - одно удовольствие, могу подтвердить.
Не стал уточнять сразу - но в микросервисной архитектуре я Rust вполне вижу. Он действительно очень удобен в таком виде, а этап привыкания и борьбы с компилятором и либами достаточно небольшой.
Правда размер бинарников (debug), объем артефактов сборки (десятки и стони гигов), длительность компиляции (я понял, зачем мне 16 ядер и 64Gb RAM) - могут сыграть не в пользу Rust и тут ))) Но это больше вопрос техники.
раньше их плагин Rust основывался на LSP.
У них уже довольно давно своя реализация, не rust-analyzer.
Возможно.
Я скорее основываюсь на впечатлениях, а не на твердом знании "что там под капотом".
Во времена VS Code с RLS существенной разницы c Rust-плагином я не увидел (возможно совсем небольшое преимущество было за JetBrains - деталей не помню).
Во времена VS Code с rust-analyzer существенной разницы c Rust-плагином я не увидел.
В переходный период Rust-плагин JetBrains отставал, как отставал RLS от rust-analyzer.
Но я все возможности досконально не сравнивал - только подсветку типов, да авто-дополнение (то, с чем работаешь больше всего и что можно быстро сравнить). В идеале, конечно, надо было погонять IDE "и в хвост, и в гриву", для формирования более целостного впечатления.
И да, это было достаточно давно - как там сейчас обстоят дела я не скажу.
В плане веба большой монолит чаще (но не всегда) считается плохой практикой. Но вот самый большой проект на rust в одном репозитории (не веб), что я знаю это сам компилятор языка. Там по сути много маленьких crates, которые прилинкованы к корневому workspace. Кажется что лепить что-то большое можно, хоть я и не пробовал лично)
В плане "железа" для rust проектов экономить не приходится. Ведь если с нуля, то компилируются все все зависимости.
"Большой монолит" когда-то был достаточно безальтернативной практикой )
Микросервисный подход получил популярность "всего лишь" лет 10 назад... Лишь с появлением и развитием Docker он стал популярен - когда управлять кучей сервисов стало сильно проще.
Впрочем, и сейчас монолит может сильно выиграть в плане производительности - так что его век может еще и вернется...
В плане артефактов сборки - проблема усугубляется тем, что под разные версии компилятора и под разные версии зависимостей - зависимости компилируются в отдельные файлы. Поэтому со временем target только растет. А уж если использовать свежие ночные сборки компилятора...
P.S. а еще есть очень холиварный вопрос - когда микросервис перестает быть микро?
Впрочем, и сейчас монолит может сильно выиграть в плане производительности
Согласен, согласен. Тут общего мнения нет. It depends.
В плане веба большой монолит чаще (но не всегда) считается плохой практикой.
Что бы холивары не разводить, поэтому так и выразился) "Маленький/средний монолит" точно считаю нормой.
когда микросервис перестает быть микро?
А это вечный вопрос)
А вот насчёт безальтернативности - не факт. К примеру, в той же WS-Addressing такая штука как Reference Parameters идеально подходит для передачи контекста запроса между микросервисами. Не могли же её придумать с потолка? Значит, нечто похожее на микросервисы писалось, пусть и называлось по-другому.
Безотносительно Раста, такие статьи, подсознательно относишь как к рекламе Гербалайфа. Слишком хорошо, что бы быть правдой. Автор, конечно же не виноват, но...
Нет причин не проверить это самостоятельно, за Rust ни в какой форме денег не берут)
P.s. если кто-то хочет мне платить за рекламу Rust (хотя бы шоколадными медальками) то пишите в личку!
шоколадными медальками
Уже после этой статьи риск заработать себе диабет был бы невероятно высок, я б не торопился с шоколадными медальками)
На самом деле, поделюсь идеей. Мне как и многим было бы интересно увидеть эксперимент от Rust разработчика, по переходу на C++(Не C и не C с классами).
Хотя бы пару месяцев его по изучать, и попытаться что-то написать. Потом сделать статью с выводами и сравнением с родным языком.
Такая мысль появилась т.к практически все кто сравнивают Rust с C++ или не дай бог с C/C++(😁) не состоят в хабе C++, и по всей видимости никогда на нем ничего серьезного не писали. Это действительно было бы интересно.
риск заработать себе диабет
Уж лучше диабет, чем расстройство нервной системы. Да и не думаю, что в статье о переходе Rust -> С++ есть большой смысл. Я гарантирую, что мой C++ код будет ужасен и состоять из UB процентов на 70. Только это не докажет, что Rust лучше (или C++ хуже), а только то, что я лично не умею писать код на C++. Что очень легко прочитать, как "Rust разработчики не умею писать код на C++", что в свою очередь можно прочитать, как "Rust разработчики не умею писать код". Это уже тут в комментариях происходит, что же будет в такой специализированной статье.
Вот обратное было бы интересно. Опытный C++ разработчик пишет некий сервис на C++. Потом N месяцев учит Rust и пишет аналогичный сервис на Rust. Там можно сравнить и код, и ощущения разработчика.Я бы такое почитал. Вообще такое сделал Google и результаты для C++ неутешительные.
Вообще такое сделал Google и результаты для C++ неутешительные.
Еще бы выступавший на Rust Nation сказал что-то другое :))) Его бы туда просто не пустили. Могу сказать свои ощущения (я могу свободно писать на C++ и на расте, и еще на ряде языков) - для себя я практически не нахожу применения для раста. Он мне после C++ банально не удобен и не выразителен. Пропагандировать что-то и навязывать кому-то свое мнение я не хочу, но оно вот такое.
P.S. А, вот даже нашел свой давнишний комментарий на эту тему. С тех пор принципиально ничего не поменялось - применений для раста в своих разработках по-прежнему не вижу.
Смысл не в переходе на C++, а в том чтоб стать лучше как разработчик и поделиться опытом с остальными.
Пока что, вы доверяете собственное мнение чужим людям. И используете ссылки на мнение других людей, как аргумент, прочёл все комментарии тут.
Ну а UB в 70%, это просто странно. Используйте безопасные типы, которые предназначены для решения Ваших кейсов и все будет работать не хуже любого другого языка. Не доверяете кому-то и даже STL, пишите сами (чего делать не нужно, но можно).
У C++ много проблем, особенно что касается сборки и линковки, геморрой ещё тот. Смотришь на то как это реализованно в молодых языках, и проступает скупая слеза. Но я практически не увидел людей ссылающиеся на эти проблемы в своих доводах о преимуществе Rust.
C/C++
(Naked Gun facepalm) - уважаемые эксперты в языках программирования, вы можете писать два РАЗНЫХ языка программирования раздельно? Удручает, что читатель в вводится в заблуждение таким образом. Язык C++ содержит только небольшую часть языка С (к сожалению приводящую к проблемам, когда используется не для легаси кода). Сам язык раскрывается именно, когда грамотно используются другие его части(под-языки):
Классы
Шаблоны
Метапрограммирование
см. C++ Core Guidelines и интервью Бьярна Страуструпа.
Не переживайте вы так, просто в процессе редактирования мне очень захотелось убрать C и/или C++
(а до редактирования этого было гораздо больше).
И да, эта сноска есть в статье, но она под двумя спойлерами. Из меня эксперт в языках примерно такой же, как и из табуретки. Я даже не уверен, кто сможет на большее количество вопросов правильно ответить.
Как удобненько, 40 лет говорить про возможность скомпилировать С компилятором С++ (99% кода), но как только говорят о C/C++ - так это же "как это?? как вы могли?!". Отвратительное лицемерие.
возможность скомпилировать С компилятором С++ (99% кода),
Современный C и подмножество C в C++ уже давно разошлись, и так делать нельзя. Не, ну если очень хочется, то можно, но результат вас может не порадовать. И это даже не про новые ключевые слова в свежих версиях C, нет, из самого простого, но неявного: type puning через union допустим (и очень часто используется) в C, но в C++ он является undefined behaviour потому что нарушает active member rule.
А "40 лет" обычно говорят не про "возможность компилировать", а про "возможность использовать" код библиотеки/модуля на одном языке в программе/модуле на другом языке (интероперабильность по ABI) и про лёгкую адаптацию кода если надо перенести фрагмент из одного проекта в другой (хотя вероятно такой код окажется кривым и не идиоматическим).
где вы были 40 лет?
извините
но я её так и не дочитал до конца, не говоря уже о том, чтобы «выучить» C. Как ни странно, но «виноваты» в этом холивары на хабре.
Так вот кто топит за новомодные языки и кидается фекалиями в Си. Те, кто его даже не удосужились выучить и не писали на нем ничего сложнее hello world.
Но еще больше резанули слова про хабр и холивары. Да, это эйджизм и субъективщина, но когда я представляю себе человека, делающего первые шаги в Си, до появления на свет Хабра еще лет 10—15 впереди. А тут человек добрался до Си когда Хабр не только появился на свет, но и на нём успели наплодить холиваров: то есть буквально вчера он начал программировать по меркам срока существования Си. Но уже занялся агитацией против Си.
И вот что я думаю: это поколенческое. Каждому новому поколению свойственно демонстрировать протест против предшествующего поколения: а мы слушаем другую музыку, а не это ваше старперское говно, а мы одеваемся по другому, а у нас свой непонятный родителям молодежный сленг. Такой вот бунт поколения против предшествующего поколения. И мне кажется, что появление за последние 10—15 лет кучи новых ЯП, про которые раньше никто слышать не слышал, это такой же бунт против предыдущего поколения. Мы разрушим цивилизацию наших предков и на обломках построим новый модно-молодёжный мир. И весь софт и все ОС перепишем на Rust'е, а куда не дотянется Rust — на Python'е конечно же (даром что ли реклама его курсов вставлена даже в видео про садоводство?). Заодно избавимся от мерзких master/slave, а красно-черное дерево переименуем в красно-афроамериканское. Просто подросло новое поколение программистов, и им массово чуется, что Си это язык старперов. Ну как мы можем программировать на том же, на чем программировали 35 лет назад? Это же как в 2024 ездить на машине 80-го года выпуска... Вот и начинается поиск или изобретение чего-то, что придало бы собственной идентичности и позволило бы иметь меньше общего с «динозаврами». Под соусом борьбы за все хорошее и против всего плохого, конечно же, хотя мотивы, как мне все больше и больше кажется, тут совсем другие.
Так это и в обратную сторону работает. Сидят "деды", медленно заростают мхом, а мир вокруг для них развивается слишком быстро, потому что нейроны в голове уже не настолько эффективно складываются в новые цепочки, как раньше, а тут ещё и молодёжь на пятки наступает — при таких вводных сложно признать, что какие-то "новомодные штуки" доказали свою жизнеспособность и имеют существенные преимущества перед старыми, проще обвинить во всем эйджизм и "бунт поколений во имя идентичности".
развивается
жизнеспособность
Раковая опухоль тоже развивается, и тоже показывает жизнеспособность. Демонстрация обоих этих факторов ещё не означает, что явление в целом хорошее.
Как удобно вы пропустили часть про "имеют существенные преимущества"
С точки зрения раковой опухоли она тоже имеет массу преимуществ по сравнению с обычными клетками. Те — просто жалкие отсталые тормоза в её парадигме.
Вы настолько ударились в свою дешёвую демагогию что полностью отошли от темы дискуссии.
«Существенные преимущества» — это субъективная категория, в отличие от «развивается» и «показывает жизнеспособность».
У каждого своё понимание существенных преимуществ. По мне так у Си есть существеннейшее преимущество, которое перешибает вообще всё на свете — ему сто лет в обед. Это как арабские цифры. Ими все пользуются, они укоренились в своей нише.
И тут приходит Вася Пупкин с тезисом «ваши арабские цифры гадость, потому что 3 похоже на 8, и это может иметь далеко идущие печальные последствия, и вот смотрите какие хорошие новые цифры я предлагаю вам — давайте все дружно переходить на их использование». Лет 15 назад таких революционеров посылали подальше, крутя пальцем у виска, а сейчас (увы и ах) ситуация поменялась: Вася Пупкин, агитирующий за за новые прекрасные пупкинские цифры вместо старых убогих арабских, не просто приходит, а ещё говорит: «смотрите, за мной стоит нетоксичное коммьюнити, и нас уже миллион».
Однако арабские цифры именно что пришли не на пустое место и укоренились, потому что имели существенные преимущества.
Возможно, вы думаете, что всё на свете продвигается только за счёт того, что кому-то заняться больше нечем, чем продвигать что-то новое только потому что оно новое, а не потому что новое действительно имеет преимущества (и, кстати, всё равно требует продвижения, даже с преимуществами), но это не так. Люди вкладывают усилия и энтузиазм только когда верят в это.
Да, но чтобы отнести некоторое явление к классу "раковых опухолей" - этих факторов тоже недостаточно. И даже личной неприязни к ним - тоже.
А тут человек добрался до Си когда Хабр не только появился на свет, но и на нём успели наплодить холиваров: то есть буквально вчера он начал программировать по меркам срока существования Си. Но уже занялся агитацией против Си.
Извините, а где вы тут увидели агитацию против Си? Или тут работает подход "кто не с нами, тот против нас", и агитация за любой другой язык равна агитации против Си?
Как же «где?»
В словах, что автор его так и не выучил (дескать, сложный), в словах про холивары (дескать, много отрицательных мнений), в словах про жонглировать бензопилами (дескать, даже не суйся — беда практически неминуема).
Вкладываете свои домыслы в чужие уста.
То есть если гипотетический я не смог выучить Си - я не имею права об этом говорить, потому что это будет агитацией против Си, а агитировать против Си могут только те, кто его знает в совершенстве?
Интересно, а это с любым языком работает, или только с Си?
Зачем вы передёргиваете?
Статья же не о том, что «я не смог выучить Си, пожалейте меня». И не о том, что «я не смогу выучить Си, зато смотрите, какие вещи я могу вытворять в баш-скриптах» (Си и баш-скрипты имеют разные ниши применений). А вот когда у инструментов почти одинаковые ниши применений и они рассматриваются как конкуренты, высказывания типа «я не смог овладеть X, зато смотрите какая прелесть этот Y» это именно что агитация за Y и против X.
И что плохого в агитации основанной на фактах? Даже "я не смог совладать с Х, а смог с Y" -- это факт, говорящий о том, что людям похожим на автора Y будет проще освоить, чем X.
Почему вы это воспринимаете в штыки? "Я вот освоил X и у меня ничего не болит, значит Y -- фигня" -- это у кого тут ещё агитация получается :)
Хорошо, уточню вопрос.
То есть если гипотетический я не смог выучить Си, но смог выучить Rust - я не имею права об этом говорить, потому что это будет агитацией против Си, а агитировать против Си могут только те, кто его знает в совершенстве?
Интересно, а это с любой парой однонишевых языков работает, или только с Си и Rust? Могу ли я "затыкать" любых комментаторов-сишников словами "не смейте агитировать против Rust пока сами его не выучите"?
если гипотетический я не смог выучить Си, но смог выучить Rust
Сама ситуация (даже гипотетическая) — уже надуманная, потому что Си чрезвычайно прост (и этим и хорош), Раст значительно сложнее. Поэтому это звучит как «представим число, которое меньше 10, но больше миллиона».
Но окей, представим, что так.
Имеете права писать о своих успехах в освоении языков, но писать статьи, в которые заложено сравнение и сопоставление (а оно заложено даже в заголовок данной статьи) двух языков, при этом бравируя, что одного из них вы не знаете, конечно не стоит.
Потому что иначе это получается классическое «Пастернака не читал, но осуждаю».
Часть про "Как бы я не относился к C, все же ему больше 50 уже, не те времена были" вы, видимо, упустили. Странно было бы предъявлять к языку 1972 года претензии на счет нововведений 21 века, я этого, мне кажется, и не делаю (хотя слышал, что даже в свое время в этом плане C не блистал и Fortran и Pascal во многом уже тогда были лучше).
Было бы интересно представить мир, в котором Rust существовал был уже 30 лет, а C++ только появился и как бы тогда выглядели споры о его преимуществах.
"Смотри, ты не понимаешь, токсичный rust'оман, здесь нет единой системы сборки, всяких пакетов и т.д., надо просто использовать собственные костыли -- хочешь make, хочешь cmake, хочешь просто руками вызывай компилятор. Да, файлы просто целиком включаются в виде текста. И нужно вручную разделять на код и заголовки, ну потому что так прикольнее и гибкости больше. Ещё никаких unsafe не надо, код может везде стрелять себе в ногу. Ну естественно тут есть десять способов делать одно и тоже, зато у вас ещё больше знаков препинания в синтаксисе."
Hidden text
![Мир, в котором Расту уже 30 лет, а C++ так и не изобрели Мир, в котором Расту уже 30 лет, а C++ так и не изобрели](https://webcf.waybackmachine.org/web/20240403173053im_/https://habrastorage.org/getpro/habr/upload_files/f5f/ec8/66d/f5fec866d7a1f3c7036d98702b3eaab5.png)
Простите, не удержался.
Если Rust будет придерживаться обратной совместимости как C++ то через 30 лет также обрастет кучей "старых подходов", "сахарком", "библиотеками написанными черт пойми как" и т.п. На мой взгляд любое сравнение давно существующего языка с новым которым решил какие-то проблемы которые есть в старом абсолютно бессмысленно. Через 30 лет существования Rust-а появиться новый язык который также решит какие-то проблемы Rust-а которые накопились за эти 30 лет его существования.
Конечно накопятся разные проблемы, но тех, которые заложены в сами основы языков и культуры С/С++ точно не появятся. Если конечно разработчики не сойдут с ума и не развернут эволюцию языка на 180 градусов.
Конечно накопятся разные проблемы, но тех, которые заложены в сами основы языков и культуры С/С++ точно не появятся.
Они решены на момент создания языка, конечно они не появятся. Еще раз мысль в том что 30 лет назад когда С++ стал тем кем он стал не существовало той критической массы знаний и подходов которые есть сейчас и даже цели создания были другими, С++ это продукт своего времени, да сейчас он кажется сложным/странным/неудобным/неочевидным, но через 30 лет Rust также будет таким же старым языком потому что мир не стоит на месте.
У раста есть механизм edition, позволяющий менять язык, отбрасывая груз старых проблем.
У раста есть механизм edition
Это прекрасно, и во все библиотеки накатывается автоматически? Потому что если нет то проблему старых библиотек это не решает. Рентабельность любого подобного механизма покажет себя только через годы когда будет все еще что-то написанное ниже на нцать версий языка и надо будет это что-то мигрировать на последнюю версию. Или если автор библиотеки считает что ему не нужна новая версия языка и он хочет навсегда остаться на одной.
Таки решает :) Потому что старые библиотеки вольны оставаться на старой редакции языка, и при этом могут быть спокойно использованы в новых программах, за редкими исключениями. Для программы/библиотеки в конфигурационном файле указывается, для какой редакции языка она написана, и компилятор полноценно и бесшовно поддерживает все старые редакции.
Проблемы старых библиотек не в том что они не компилируются а в том что они написаны на старых редакциях языка с использованием "старых подходов".
Так гарантии, которые предоставляет Rust, есть уже с 1.0 (2015 года). От того, что в библиотеке старые подходы она хуже работать не станет.
Тут возможна проблема тогда, когда у библиотеки не обновляются зависимости и в части из них могли найти и исправить баги. Это да, но это относится не ко всем библиотекам, многие из них нет никакого смысла переводить на новую редакцию, от самого этого факта ничего не изменится.
От того, что в библиотеке старые подходы она хуже работать не станет.
Библиотека хуже работать не станет но если Вам вдруг по каким-то причинам понадобиться открыть ее и разобраться с багом каким-нибудь или даже доработать под себя потому что ее давно не обновляли вот тогда то и начнутся трудности. И чем она старее она тем больнее с ней будет работать.
Можете, пожалуйста, привести пример, что Вы имеете в виду?
Каждая библиотека имеет свой собственный edition и т.к. каждый крейт - отдельный юнит трансляции они не страдают из-за этой разницы. Есть ещё так называемая политика MSRV - минимальная поддерживаемая версия раста. Так что каждый может писать независимо пока версия компилятора позволяет это делать.
Хоть сам и пишу на Расте сейчас в основном, но вот рассказы о его безопасности почти сразу разбиваются об огромное количество unsafe
как в стандартной библиотеке, так и во всяких tokio
и иже с ними.
И хоть там "мамой клянус" всё на самом деле безопасно, но доверять в итоге приходится авторам библиотеки, а не компилятору который бы это подтвердил.
Какой-нибудь async_scoped у нас используется местами, а там всё в unsafe
внутри с комментариями вроде:
/// # Safety
///
/// This function is _not completely safe_: please see
/// `cancellation_soundness` in [tests.rs][tests-src] for a
/// test-case that suggests how this can lead to invalid
/// memory access if not dealt with care.
На деле я редко встречался с реальными проблемами там, но это уже заслуги не языка, а авторов библиотек, которые тщательно стараются писать unsafe
-блоки.
На деле я редко встречался с реальными проблемами там, но это уже заслуги не языка, а авторов библиотек, которые тщательно стараются писать unsafe-блоки.
Ну так именно в этом и идея unsafe же. В чём тут проблема-то?
В том, что он используется направо и налево. И это снижает общую безопасность, декларируемую компилятором.
Смотрите на unsafe не как на способ пострелять в ногу, а как на механизм ограничения областей, где существует возможность отстрелить ногу.
Я понимаю что такое unsafe и как им пользоваться. Но, ещё раз, его очень частое использование в самых разных крейтах снижает общее доверие к гарантиям компилятора.
А т.к. писать код без зависимостей могут только академики, да и те вряд-ли, то приходится надеятся что все пограничные случаи в unsafe-ах твоих зависимостей учтены.
Можно открыть и самому проверить эти места. Что-то я из своей практики не припоминаю проблем с unsafe в зависимостях. В основном все стремятся минимизировать число использований unsafe в своих библиотеках.
Благодаря тому, что в Расте unsafe явно отделён, то можно автоматически изучать подобные участки, определять практики, которые программисты реально используют, оценивать обоснованные ли они, предлагать автоматические подсказки по исправлению, предоставлять формальные гарантии и т.д.
приходится надеятся что все пограничные случаи в unsafe-ах твоих зависимостей учтены
Я так и делаю, когда использую зависимости в C++, потому что иначе придётся просматривать вообще весь код, а жизнь у меня только одна.
Нет, не разбиваются :). Ведь абстрактной абсолютной безопасности не обещали. Идея Раста - не связать программиста по рукам и ногам, а сделать так, чтобы стимулировать его писать беспроблемно, и в случаях, когда он выходит на "тонкий лёд", он делал это сознательно и, соответственно, осторожно.
Блоки unsafe - немногочисленны и невелики. Соответственно, чем меньше небезопасного кода, тем меньше вероятность проблем уже из чисто количественных соображений. Но вероятность пробоем ещё ниже из-за того, что unsafe - это своего рода красные флажки. Программист естественно пишет эти блоки с бОльшим вниманием, чем остальной код. Одновременно и при аудите кода или при отладке они указывают, на что имеет смысл обратить пристальное внимание.
Это касается не только unsafe, это фундаментальная парадигма. Например, можно не обрабатывать ошибки и использовать unwrap. Но это тоже красный флажок, навешиваемый сознательно.
Аналогичный подход не только с небезопасными действиями, но и с ресурсозатратными. Обычно, когда делается неочевидно затратное действие, это должно сопровождаться явно указывающим на это текстом. Классический пример этого - использование clone().
Специально для вас у меня есть ссылка (там первые минут 10 про это), которая все это недопонимание объяснит! Тот факт, что в языке и библиотеках на порядки меньше уязвимостей, чем в C и/или C++ и то, что сообщество очень пристально следит за тем, чтобы в публичных библиотеках не использовался unsafe код направо и налево дает очень хорошую гарантию. Так то никакой язык не безопасный, Python внутри на C, но при этом UB там получить крайне маловероятно.
В С++ внезапно тоже можно писать отдельно "безопасные" участки и "небезопасные", вау.
С таким же успехом можно просто сказать, что все места где *ptr и reinterpret_cast являются unsafe, к ним нужно пристальное внимание и тд.
То что я вижу - отличие исключительно на словах. Т.е. в С++ ты якобы это делать не можешь, а в Rust ты не можешь просто писать внутри каждой функции unsafe и наружу выставлять safe интерфейс
Паники - то же самое, все растеры не устают повторять, что якобы это unrecoverable error, совсем не как исключения в С++, но при этом все библиотеки написаны так что учитывают поимку паник, есть собственно возможность ловить паники, паники прокидываются из тредпула в Rayon (такого в С++ не делают по причинам адекватности), то есть на деле получается, что никакого отличия нет, просто раст назвал это по другому и якобы это лучше
Обработка ошибок: раст назвал свои коды ошибок вопросиком (?) и вместо написания ничего (использование исключений) пишет.unwrap руками в каждой строке, а потом сказал, что это не просто коды ошибок, а видите ли монады или какие там умные слова они ещё используют, такого якобы нигде не было и никогда!
В С++ проблемные места, увы, не ограничиваются разыменованием указателей и reinterpret_cast.
Ну хорошо, вот вам проблемное место:
class Foo {
// …
void bar() {
baz->execute([=] () {
this->qux++;
});
}
}
Точнее, это место является проблемным если функция baz->execute асинхронная, но не является таковым если она никуда не сохраняет полученное замыкание.
Что именно тут указывает на необходимость обратить сюда пристальное внимание?
C Rust не сталкивался ни разу, так что не буду рассуждать о нем ни в положительном, ни в отрицательном ключе.
Но вот какие мысли возникли по прочтении.
В начале был упомянут некий "mission critical". Поскольку сам работаю именно с таким кодом, то есть понимание того, что это не только безопасный и надежный, но еще и высокоэффективный код. Т.е. тот код, который по производительности, нагрузке на машину как справлялся с 25млн "условных клиентов", так и продолжает справляться когда их стало 50млн. Справляется в данном случае - укладывается в заданное временное окно, не вызывая задержек в остальных работающих в системе процессов и не вызывает критического увеличения нагрузки на машину. И в этом ключе ожидалось увидеть упоминание о нагрузочном тестировании как неотъемлемой части процесса разработки. Но... Нет. Не увидел. А без нагрузочных тестов не может быть и речи о каких-либо преимуществах в "быстродействии", "эффективности" и т.п.
Также было упомянуто что автор "боится С". Причем, боится не осознанно, а от непонимания как оно работает. В дальнейшем подтверждается мучениями с мьютексами и т.п. Т.е. просто "не знаю, не понимаю как он устроено внутри". Зато радость когда нашел библиотеку где об этом не надо думать. А есть уверенность что эта библиотека одинакова хороша на все случаи жизни? Есть понимание что "многопоточную обработку" можно реализовать не только на пуле потоков, но и на пуле процессов? И есть ситуации, когда такой подход будет предпочтительнее. И обмен данными между потоками - это далеко не всегда разделяемая память и блокировки. Есть и другие механизмы, которые таже могут быть использованы в ряде случаев. И что выбор наиболее эффективного подхода всегда зависит от конкретной задачи. Но нет - есть замечательная библиотека, дальше можно не думать.
Вообще от таких статей всегда хочется конкретного сравнительного анализа - "на этом языке это реализуется так, а на том - этак" При этом "тут используется столько-то ресурсов, там столько-то". "Тут задача выполняется за такое-то количество времени, там за такое-то". Ну что-то в этом духе.
Вместо всего этого приведена куча каких-то технических частностей.
Вообще от таких статей всегда хочется конкретного сравнительного анализа - "на этом языке это реализуется так, а на том - этак" При этом "тут используется столько-то ресурсов, там столько-то". "Тут задача выполняется за такое-то количество времени, там за такое-то". Ну что-то в этом духе.
Большинство языков одного уровня (например компилируемые, системные, допустим с натяжкой C++/Rust/Go можно в один ряд поставить) уже, мне кажется, давно на примерно одном уровне производительности и потребления ресурсов.
Поэтому на первый план выходит именно безопасность, а точнее простота написания безопасного (в каком-либо смысле или смыслах) кода.
Также было упомянуто что автор "боится С". Причем, боится не осознанно, а от непонимания как оно работает. В дальнейшем подтверждается мучениями с мьютексами и т.п. Т.е. просто "не знаю, не понимаю как он устроено внутри".
Я т.к. в этом варюсь уже лет 20+ более менее понимаю как это всё устроено, но от этого желание ручками ковырять всё на низком уровне редко возникает. Если можно абстрагироваться почти бесплатно - почему бы и нет.
Не так давно пришлось пару модулей писать под nginx
на Си (впервые за много лет) - ух, как меня трясло от отсутствия давно привычных вещей вроде контроля выхода за границы массива и прочего.
И всякие (не)смешные приколы вроде того, что моя функция совпадала по названию (но не сигнатуре) с функцией из zlib
, которая была слинкована с nginx
. И в итоге вызывалась функция оттуда, а не из моего модуля, и всё падало. Хотя казалось бы... ни компилятор, ни линковщик не смутились.
Большинство языков одного уровня (например компилируемые, системные, допустим с натяжкой C++/Rust/Go можно в один ряд поставить) уже, мне кажется, давно на примерно одном уровне производительности и потребления ресурсов.
Если говорить о качестве исполняемого кода - наверное да. Но если говорить о идеологии и подходах...
Как пример - динамическая работа с памятью. Активное выделение-освобождение памяти на больших (десятки и сотни миллионов) выборках уже становится заметным на нагрузочных тестах. Т.е. те языки (и библиотеки), которые это активно используют тут проигрывают (сюда же - создание и инициализация объектов в рантайме, сюда же передача разного рода "безопасных копий" объектов в функции).
Если сравнивать два языка с одинаковыми подходами - разницы не увидите. Если подходы разные (в одном используется статика по максимуму) - разница в производительности будет.
Понятно, что динамическая память позволяет ее экономить. Но ценой увеличенного потребления ресурсов процессора. Дальше уже смотрим что нам важнее здесь и сейчас.
Аналогично разного рода GC. Оно тоже не даром дается.
Не так давно пришлось пару модулей писать под
nginx
на Си (впервые за много лет) - ух, как меня трясло от отсутствия давно привычных вещей вроде контроля выхода за границы массива и прочего.
Если этот контроль в рантайме, то он дается ценой некоторого количества дополнительного кода. На выполнение которого требуется некоторое время. А на одном "обороте" цикла не заметите. На 100 000 000 - увидите.
Когда у вас задачки типа "сравнить два множества - одно 250 000 000 элементов, второе ~1 000 000 элементов на совпадения" (причем совпадение - это не тупое a = b, а сравнение двух поднаборов разного размера на присутствие всех уникальных элементов меньшего по размеру поднабора в большем по размеру) - тут уже приходится на каждой спичке экономить. Любой лишнее телодвижение помноженное на количество циклов становится ощутимым.
Так что когда тут используешь какие-то библиотеки, то всегда думаешь - а как оно там внутри работает? А нет ли там чего лишнего, того что для универсальности добавлено, но в данном конкретном случае нафиг не нужно...
Когда у вас задачки типа ... тут уже приходится на каждой спичке экономить
И даже тут есть горячий код, в котором лишние проверки очень мешают - и не такой горячий, в котором лишние проверки не заметны на фоне первого.
Если сравнивать два языка с одинаковыми подходами - разницы не увидите. Если подходы разные (в одном используется статика по максимуму) - разница в производительности будет.
Почти везде можно в горячих участках уйти в ручное или полу-ручное управление аллокациями. Даже в Go в проектах вроде fasthttp
активно используется пулинг объектов через тот же sync.Pool
, чтобы не нагружать аллокатор и сборщик мусора лишними думами.
Поэтому, на мой взгляд, возможность писать 99% кода в безопасном режиме и в 1% горячих путей взять какой-нибудь unsafe
и ручками сделать хорошо - неплохой компромисс.
Если этот контроль в рантайме, то он дается ценой некоторого количества дополнительного кода. На выполнение которого требуется некоторое время. А на одном "обороте" цикла не заметите. На 100 000 000 - увидите.
Само собой, у всего своя цена. Но, опять же, см. выше, я бы хотел по-умолчанию делать удобно, а если мне нужно быстро - я переключусь в ручное управление. В том же расте есть всякие smallvec
и иже с ними, которые аллоцируют вектора на стеке до определённого размера и реаллоцируются в хип только при переполнении.
В общем и целом большая часть софта не требует экономии на спичках. Но в каких-то местах это нужно и возможность должна быть, согласен.
Поэтому, на мой взгляд, возможность писать 99% кода в безопасном режиме и в 1% горячих путей взять какой-нибудь
unsafe
и ручками сделать хорошо - неплохой компромисс.
Проблемы начинаются когда соотношение 99/1 меняется к наоборот - 1/99. Ну просто так сложилось - где 99/1 отдают тем кто помоложе, а где 1/99 - вам. Потому что "вы умный, вы справитесь, мы в вас верим".
В общем и целом большая часть софта не требует экономии на спичках. Но в каких-то местах это нужно и возможность должна быть, согласен.
Да. Большая часть софта (если смотреть вообще весь софт который пишется) вообще мало чего требует.
Но максимальное количество болей доставляет именно та малая часть, где все очень критично.
Проблемы начинаются когда соотношение 99/1 меняется к наоборот - 1/99. Ну просто так сложилось - где 99/1 отдают тем кто помоложе, а где 1/99 - вам. Потому что "вы умный, вы справитесь, мы в вас верим".
То есть где-то в теории, а не на практике, да?
According to Stanford, around 5% of Rust code is unsafe, including around 30% of crates. According to The New Stack, the percentage of unsafe functions in crates.io is 7.5%, while in Redox it is 1.7%, and in rustc it is 31.1%. However, if you exclude the core-arch crate, which defines 94.6% of unsafe functions, the percentage of unsafe functions in other crates is 2.7%.
Т.е. unsafe используется редко, но метко, там где не обойтись -- в работе с другими языками, железом и глубинах самого языка.
Нет, не начинаются. Потому что, во-первых, считать проценты надо по всей кодовой базе, а не по смещённой выборке. А во-вторых, ради этого же всё и затевалось-то! Чтобы можно было отдать самые сложные части самому опытному программисту, и у него не болела голова о том, что кто-то из менее опытных коллег где-то там неправильно использует то что он написал.
В Rust такая же как в плюсах идеология zero cost abstractions и стремление к твоему э такому дизайну языка чтобы не платить (в рантайме) за то, чем ты не пользуешься.
Так что он вполне подходит для написания высокоэффективного кода. Как пример, можно посмотреть на GPU драйвер Asahi linux для Apple Silicon. Он работает весьма и весьма хорошо, а его ядерная часть написана на Rust. И по словам разработчиков, выбор языка избавил их от долгой и сложной отладки. Помню была статья где они от уровня крутить шестерёнки перешли на уровень запуска KDE всего за пару дней.
К слову, этот драйвер имеет сертифицированную поддержку Opengl 4.6, в отличие от оригинального от Apple
Не согласен, что "mission critical" обязан быть высокоэффективным. Код установок для рентгеновского излучения очень даже "mission critical", но 25млн. человек ему одновременно обслуживать не надо.
Эта статья и так огромная, я половину статьи выкинул и еще половину от оставшегося засунул под разные спойлеры, чтобы их можно было пропустить если не интересно. Если я начну говорить о всем, что в Rust хорошо, то эта статья бы: 1) была бы раз в 5 больше; 2) вряд ли когда-либо вышла, мне и эти мысли оформить в статью было непросто. Про скорость работы Rust есть куча других статей, если вкратце, то он +- аналогичен по скорости с C. У меня же была другая задача, я хотел показать, что как раз кроме скорости в Rust есть очень много всего.
Как раз таки наоборот, после знакомства с Rust и более полного понимания "как же оно внутри устроено" я "боюсь" C и/или C++ еще больше. Огромное количество способов потратить недели своей жизни на поиск очередного UB в абсолютно обычном коде бизнес-логики (а не в коде супер-оптимизированных алгоритмов) для меня просто неприемлемо.
"Зато радость когда нашел библиотеку"
Видимо, я действительно неправильно расставил акценты, суть не в том, что есть чудо библиотека, в которой есть чудо из чудес - пул потоков. Суть в том, что не важно с использованием библиотеки или без в Rust невозможны гонки данных, что дает свободу экспериментировать с алгоритмами без мьютексов, атомиков и прочего. Если я ошибусь - код не скомпилируется. Если код скомпилировался - у меня есть гарантия того, что огромного числа проблем в моем многопоточном коде нет.Статей про прямое сравнение по разным параметрам тоже огромное количество, можете без особых проблем их найти. Мне не хотелось писать "тоже самое, чуть другое".
Эт не mission critical, это safety critical. Впрочем, мишн критикал действительно не обязан быть высоконагруженным.
Суть в том, что не важно с использованием библиотеки или без в Rust невозможны гонки данных
Гонки данных (и прочие дедлоки) - это прежде всего ошибка архитектуры. То, что их нет в Rust не совсем верно. Их нет в реализации конкретной библиотеки.
А если говорить о параллельной обработке, то это не всегда совместная работа с одними и теми же данными. Это может быть обработка больших объемов данных, где каждый элемент обрабатывается независимо от остальных. И там не будет никаких гонок. Но и подход будет совсем иным в плане организации потоков, их балансировки и распределения по ним обрабатываемых данных.
Или это может быть конвейерная обработка пакетов где каждый поток выполняет свою часть работы (с тем, чтобы максимально быстро освободиться для работы со следующим пакетом) и передает пакет дальше по конвейеру.
И в каждом конкретном случае наиболее эффективным будет свое решение, а не попытка натянуть одну библиотеку на все. Но для правильного выбора нужно очень хорошо понимать как оно работает. И лучше всего один раз не пожалеть времени, сделать все руками, пройтись по всем граблям, а потом уже переходить на использование библиотек.
Лет 10 назад перевел свой проект с C++ на Python чтоб убрать компиляцию. Время полной перекомпиляции проекта - более часа. На таких проектах ты дорабатываешь какой-то модуль, ну т.е. dll-ку, которая компилируется порядка минуты. Но полный цикл отладки всё равно занимает порядка 10 минут.
остановить основное приложение, чтоб оно освободило dll-ку
скомпилировать dll-ку
запустить основное приложение - это тоже порядка минуты
проверить функционал - получить сбой
подключиться дебаггером - это нужно найти все воркеры в системе, относящиеся к основному приложению
отладится - понять что что-то не учел в ответе от другого модуля
Это мало того что медленно, из-за больших пауз теряется фокус внимания. А постоянное переключение внимания сильно утомляет.
Вместо всего этого в питоне пишешь что-то типа if is_local_stend: imp.reload(<модуль>) и у тебя следующий же запрос идет с использованием нового кода. А дебагер практически никогда не нужен (он в питоне тормозной) - при такой скорости перезапуска можно просто добавлять вывод данных в лог-файл.
Недавно видел как чувак для раста сделал хот-релоад модуль. Правишь код, компилируешь, магически обновляется поведение программы.
в Rust по умолчанию CDD (compiler-driven development, разработка через компилирование). Это как TDD, только CDD;
Т.е. сначала нужно написать компилятор под свой проект?)
не успел я эту статью выложить, как оказалось, что 27 марта 2024 года на конференции Rust Nation UK 2024 было выступление с интригующим названием Beyond Safety and Speed: How Rust Fuels Team Productivity от Lars Bergstrom, Google Android Director of Engineering
Только вот незадача, это участник Rust Foundation, причём не последний, вот такой вот "независимый" эксперт
https://foundation.rust-lang.org/news/2021-04-22-introducing-lars-bergstrom/
Насчёт самого обсуждаемой его фразы, якобы раст вдвое продуктивнее С++. Если немного покопаться в том на что он ссылается, то там было переписывание с С++ на Rust, при этом второе переписывание(!!!)
А весь его доклад состоит на 100% из слов и перевираний, секция с "примерами" закрывает "выводом" весь экран и в "идиоматичном С++" у него пишут struct fd_t fds[FD_COUNT] т.е. С89 код с использованием линукс апи, а справа десять слоёв абстракции над тем же самым на расте. И даже в таком сравнении раст код там выглядит хуже (вложенность 9 слоёв)
По поводу остальной статьи:
лежит книга «Язык программирования С», но я её так и не дочитал до конца, не говоря уже о том, чтобы «выучить» C.
Тогда на основании чего вы говорите, что Rust - это "не то же самое" или лучше в чём-то? Вы думаете в С++ нет библиотек похожих на Rayon(спойлер -есть, и они гораздо лучше)? Попробуйте скомпилировать код на С++, вы удивитесь как много ошибок компилятор таки найдёт, особенно по сравнении с Python, с которым вы и сравнивали Rust
В чём незадача-то? "Независимость" эксперта вы сами придумали, сами опровергли. Удивительно, что представитель компании широко использующей язык программирования входит в состав разработчиков этого языка и на конференции по этому языку рассказывает про реальный опыт использования этого языка в компании, ого.
вот такой вот "независимый" эксперт
Учасникам Rust Foundation теперь запрещено выступать на конференциях со своими данными?
при этом второе переписывание
Ну да. После нескольких переписываний C++ кода на C++. И после этого оказалось, что так же переписать код с C++ на Rust в двое быстрее, чем с C++ на C++.
Попробуйте скомпилировать код на С++, вы удивитесь как много ошибок компилятор таки найдёт
Как только C++ не будет компилироваться в случае, когда в коде есть гонка данных обязательно попробую.
Именно. Вопрос не в том сколько ошибок компилятор найдет. Достаточно одной, которую он не найдет...
Возьмите Erlang и наслаждайтесь. «Истинная» многопоточность из коробки. Ни каких тебе мьютексов и других примитивов синхронизации.
Добавлю Erlang в список на изучение, много про него слышал, но руки не доходили. Будет интересно посмотреть на такую реализацию многопоточности.
И да, то, что Rust в каких-то местах показывает себя очень хорошо не значит, что он там лучший и, тем более, что только Rust хорошо себя показывает. У меня с Golang проблема как раз в этом, прям супер нового и уникального в Rust не много, там просто много вещей, которые хорошо работали в других языках. Что мешало добавить хотя бы часть из них в Golang для меня загадка.
А весь его доклад состоит на 100% из слов
И более того, в его словах есть буквы!
спойлер -есть, и они гораздо лучше
а вот с этого места поподробнее. Кроме стандартных спецификаторов для алгориитмов и либы openmp ничего настолько же универсального не припоминаю.
Rayon это буквально переписывание TBB https://github.com/oneapi-src/oneTBB
Спасибо за статью, было интересно
Единственное, что смутило, это сравнение python и "многих других языков", которые прям вот отличаются от Rust
С позиции Python -> Rust, наверное яркий контраст играет свое
Но если бы это было Java(и пр. ЯП из jvm)/C# -> Rust, то тут такого контраста нет совсем
И с типизацией все более или менее в порядке, и с многопоточкой, и рефакторить вполне себе все это можно без дикой головной боли, если всё правильно написано(но думаю наговнокодить можно и в расте, да так, что потом хрен отрефакторишь)
Просто по сравнению с Java у Rust другие преимущества) Писать статью про все достоинства (да и недостатки) Rust на 7 часов непрерывного чтения я пока не готов, так что написал о том, что не представлено так ярко, как мне бы хотелось.
более или менее в порядке ... с многопоточкой
Насколько я понимаю, тех же гарантий, которые дает Rust в плане многопоточности у Java нет. Тут либо Rust, либо языки без мутабельности вообще.
Я с Rust активно познакомился уже имея существенный опыт разработки на C#/Java/Golang - и это был прямо "глоток свежего воздуха".
Rust на многие вещи смотрит по другому. Простейший пример - отсутствие null-значений и работа c enum-ами. Владение переменной - тоже очень интересный подход.
Так что контраст будет и после C#/Java/Golang. Это был очень интересный опыт.
Но рекомендовать Rust прям для всего я не буду - на мой взгляд, он очень хорош для небольших проектов (например, для микросервисов).
После C# писал на Rust, и немного расстроился от того, сколько ошибок многопоточности было в моем коде на C#/
Для бизнес приложений (например, джсон апишек) Rust менее удобен, чем C#, только отсутствием LINQ (ну и невозможностью рефлекшена в рантайме, но это нужно гораздо реже).
Пока что самое сложное в Rust начинается, когда в коде появляются всякие Cell/RefCell/Rc/Arc. Работа с односвязными списками самая большая боль и считаю, что если односвязный список появился в коде, то с большой долей вероятности кто-то что-то делает не так.
Вторая сложность - аргументирование о производительности кода. Вот эти во проблемы выбора между .iter()
, .into_iter()
и .iter_mut()
или передача аргумента по значению/по ссылке. В одних ситуациях идея об иммутабельности типов работает и ожидаемые оптимизации срабатывают, в иных случаях производительность становится неочевидной и необходимо профилировать код даже на относительно небольших программах.
Третья вещь которая связана с рефакторингом - пока ваша программа линейная и однопоточная, то проблем с ним практически никогда не возникнет, но как только появляется асинхронность или многопоточность, а в сигнатуры начинают подмешивать лайфтаймы и границы а ля Send+Sync, то производный код быстро начинает ими "отравляться" при неаккуратном использовании и рефакторить его становится кратно сложнее. Способов решения этой проблемы имеется несколько, но подозреваю, что существуют ситуации, когда "уродливый" всё равно неизбежен.
После приблизительно 200 задач порешанных на leetcode понял, что теперь могу писать на rust скрипты так же продуктивно, как и на Python, имея при этом кратно бОльшую производителность (даром компилируемый язык) и меньшую вербозность, как если бы делал это на плюсах.
самое сложное в Rust начинается, когда в коде появляются всякие Cell/RefCell/Rc/Arc
Я бы сказал наоборот, когда начинается что-то сложное появляются Cell/RefCell/Rc/Arc
. Во многих случаях правильным было бы не добавление счетчиков ссылок и interior mutability, а структурировать программу так, чтобы они не были нужны. Конечно, это не всегда возможно, но не могу сказать, что они нужны часто. Safe варианты деревьев можно так писать, а можно, например, хранить все ноды в векторе и в качестве указателя использовать позицию ноды. И универсальный совет про лекции Алексея Кладова про эту ситуацию. Вообще эти лекции очень универсальны, там можно найти ответ практически на любой вопрос. Думаю смысл жизни тоже там есть.
"уродливый" всё равно неизбежен
Тут же дело не в "уродливости" кода, а в том, что можно один вариант поменять на другой с гораздо большей уверенностью в том, что код делает тоже самое.
деревья-то понятно, один вошёл n вышло, но если это просто случайный граф да ещё и ориентированный, да ещё и с рёбрами из себя в себя, то там уже всплывают нюансы и возникает проблема "сложность" vs "количество" кода - либо будет сложно писать и убеждать компилятор, либо придётся реструктурировать код под более дата ориентированный подход с ECS и кверями, что легко и непринуждённо утроит сложную версию кода. Тут каждый решает сам по какому пути идти.
Interior mutability вполне может всплывать в многопоточном/асинхронном коде и его практически нереально исполнить как-то иначе, так что тренировка на котиках вполне оправдывает себя.
с гораздо большей уверенностью в том, что код делает тоже самое.
Это уже не столько про рефакторинг, сколько в принципе про некрасивый код, который уже оптимален по Парето - сделаешь красивее - получишь минус производительность, сделаешь быстрее - получишь нечитаемую и неподдерживаемую кашу. Говорят задачи с Advent Of Code отличный способ показать подобные ситуации, но сам не пробовал его решать.
а можно, например, хранить все ноды в векторе и в качестве указателя использовать позицию ноды.
и это почему-то считается в раст сообществе нормальным. Т.е. язык заставил сделать какую то хрень, изменить всю архитектуру приложения, получить за это потерю производительности, читаемости, поддерживаемости и тд, при этом ни один контейнер стандартной библиотеки раста не даёт стабильности ссылок на элементы, и это не случайность - такую абстракцию не способен выразить сам язык (иначе все методы были бы unsafe).
Главное, что нужно понять - на самом деле это суть раста. Все библиотеки включая стандартную и даже компилятор самого раста(фронтенд для ллвм) являются прекрасными примерами во что вырождается код при таких "разумных" требованиях языка.
То есть переход на раст даёт невозможность писать интуитивно понятный код, который работает буквально во всех других языках, но сторонники будут оправдывать это вечно (пока язык не забудут через 5-7 лет)
при этом ни один контейнер стандартной библиотеки раста не даёт стабильности ссылок на элементы, и это не случайность...
…потому что такой гарантии и правда нету.
А вот принятый подход просто использовать индексы - и правда хрень, это обход защиты вместо решения проблемы.
и это почему-то считается в раст сообществе нормальным
Простите мою иронию, но не могу сдержаться. Угадайте, откуда я взял такой подход для своей реализации Aхо-Корасика? Я взял его из обучающей статьи, где как раз C++. Понятия не имею, почему автор решил сделать так вместо указателей, но, очевидно, в C++ такой подход тоже используется. Видимо C++ тоже "заставил сделать какую то хрень, изменить всю архитектуру приложения, получить за это потерю производительности, читаемости, поддерживаемости и тд,".
Ни разу не видел чтобы в С++ так писали. А уж тем более AST строить на векторах и индексах (конечно же с проверками при обращении!)
А в компиляторе раста так и написано
Ну, теперь вы это видели.
Да, олимпиадники обычно именно так и пишут, потому что указателей боятся.
А в компиляторе раста так и написано
Я не поленился и посмотрел, как же в стандартной библиотеке Rust реализован двусвязный список. Не поверите, все на указателях, никаких векторов! BTreeMap и BTreeSet тоже на массивах внутри (т.к. это, собственно, BTree). BinaryHeap действительно в хранит ноды в векторе. Насколько я помню, так и рекомендуется для наилучшего использования CPU кеша.
А в компиляторе раста так и написано
Я не поленился и посмотрел, как же в стандартной библиотеке Rust реализован двусвязный список. Не поверите, все на указателях, никаких векторов!
Это лишний раз подчёркивает уровень понимания темы, вы считаете компилятор и стандартную библиотеку одним и тем же
P.S.
указателей там кстати нет (с точки зрения раста), вместо этого вот такой ужас:
![](https://webcf.waybackmachine.org/web/20240403173053im_/https://habrastorage.org/getpro/habr/upload_files/a13/4cb/58f/a134cb58f37f7c5f8774a30e957cf52b.png)
Option<NonNull<Node<T> > >
marker: PhantomData<Box<Node<T>, A> >
что уж лучше может показать абсурдность раста, чем такой код
Ладно, зайдем с другого конца. Вы вот правда считаете, что вы один знаете, как правильно связные списки писать? В компиляторе Rust используется даже код сторонних библиотек, не говоря уже о стандартной библиотеке. Что-то мне подсказывает, что у них были причины на то, чтобы использовать отдельную реализацию с вектором, а не готовую из стандартной библиотеки.
В компиляторе Rust используется даже код сторонних библиотек, не говоря уже о стандартной библиотеке.
что? Использовать код сторонних библиотек при реализации компилятора (как и любой другой программы) - норма, более того, компилятор раста это практически на 100% код другой библиотеки (llvm на С++). При чём тут крейт по ссылке - я не понял
Вы вот правда считаете, что вы один знаете, как правильно связные списки писать
причём тут я? Связные списки уже лет 70 люди пишут и что-то никому ни разу (до раста) не пришло в голову сломать всю концепцию подобных списков, например до некоторой версии в std::list раста ... нельзя было удалить элемент...
Вставить в середину до сих пор нельзя ну и вообще все операции, которые свойственны списку, там отсутствуют (всилу того как устроен язык и философия писателей его стандартной библиотеки)
Как предсказуемо, что разговор про раст сведётся к связным спискам, как будто на них свет клином сошёлся. Которые и на архитектуру железа отвратительно ложатся, и используются в паре калечных ситуаций и которые можно один раз написать и (реально) везде потом использовать (потому что в отличие от великого Си не надо каждому проекту писать или копи-пастить с небольшими изменениями свои костыли).
разговор про раст сведётся к связным спискам
их сам автор зачем-то привёл в пример
на архитектуру железа отвратительно ложатся
к сожалению для раста не все задачи это числодробилка внутри одного массива. Хотя... Раст же и в этом сценарии потрясающе плох - вы даже не можете взять две ссылки на разные элементы внутри вектора
Во всех других задачах образуются графы или связные списки в том или ином виде. Более того, именно что связный список "один раз написать" не получится, потому что каждый раз он для конкретного случая свой. И используется крайне часто как раз в системном программировании, на которое (напомню) раст и нацелен. Он же позиционирует себя как системный язык
Вы распространяете недостоверную информацию. Взять две ссылки на разные элементы внутри вектора можно.
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=c4605e4e02c328af428c7c4f32381424
Можно и без unstable тоже самое сделать.
Используя
расширение компилятора
уб с точки зрения языка
#noinline чтобы уб не сработало
чтобы это сделать всё равно внутри происходит !!! создание массив, сортировка и проверка на наличие повторений!!!
Саморазоблачение какое то
Эм, нет. Просто нет.
Unstable - это не расширение языка, это фичи, у которых еще не зафиксированно API и оно может поменяться со временем;
Тут нет никакого UB;
#[inline(never)]
никак не влияет на UB и на работоспособность этого кода;Попробуйте найти тут "создание массив, сортировка и проверка на наличие повторений":
Заголовок спойлера
playground::swap: # @playground::swap
# %bb.0:
mov eax, dword ptr [rdi]
mov ecx, dword ptr [rsi]
mov dword ptr [rdi], ecx
mov dword ptr [rsi], eax
ret
# -- End function
playground::main: # @playground::main
# %bb.0:
push rbx
sub rsp, 80
movabs rax, 953482739823
mov qword ptr [rsp + 4], rax
lea rsi, [rsp + 12]
mov dword ptr [rsp + 12], 333
lea rbx, [rsp + 4]
mov rdi, rbx
call playground::swap
mov qword ptr [rsp + 16], rbx
lea rax, [rip + core::array::<impl core::fmt::Debug for [T; N]>::fmt]
mov qword ptr [rsp + 24], rax
lea rax, [rip + .L__unnamed_3]
mov qword ptr [rsp + 32], rax
mov qword ptr [rsp + 40], 2
mov qword ptr [rsp + 64], 0
lea rax, [rsp + 16]
mov qword ptr [rsp + 48], rax
mov qword ptr [rsp + 56], 1
lea rdi, [rsp + 32]
call qword ptr [rip + std::io::stdio::_print@GOTPCREL]
add rsp, 80
pop rbx
ret
# -- End function
Вставить в середину до сих пор нельзя
А это по вашему что такое? Api не стабилизировано, но тут проблема в том, что не нужны связные списки. И я слышал мнение, что не стоило его и добавлять, только место занимает и время отнимает от действительно полезных вещей.
всилу того как устроен язык и философия писателей его стандартной библиотеки
Да нет, просто не сдались эти списки никому. Не нужны они в 99.9999% случаев. Если вот прям ну уж очень хочется связный список, то я уверен, что можно найти на любой вкус и цвет на crates.io.
Цитата из документации Rust:
NOTE: It is almost always better to use Vec or VecDeque because array-based containers are generally faster, more memory efficient, and make better use of CPU cache.
Да нет, просто не сдались эти списки никому.
интересно как так получается, что именно то что в расте не работает внезапно становится никому не нужно?
Если вот прям ну уж очень хочется связный список, то я уверен, что можно найти на любой вкус и цвет на crates.io.
вот именно что нет и я сказал почему - потому что таков язык
Цитата из документации Rust:
ну конечно, они же не могут написать, что "наш язык не смог вырзить ни одной полезной операции на связном списке, поэтому его использование бесполезно"
Вставка в середину связного списка это О(n), что чудовищно неэффективно.
Видимо, никому оно не пригодилось, раз не добавили.
Гм, гм, если на элемент связанного списка, после которого нужно вставить новый элемент, уже есть ссылка, то операция вставки - это O(1). Если есть только порядковый индекс i, то это O(i) - нужно "пролистать" до нужного элемента. В случае вектора же это в лучшем случае O(n-i), а в худшем (требуется реаллокация) это O(n). Связные списки имеют свои проблемы, но вот скорость вставки в середину у них ничуть не хуже, чем у вектора.
(del - я буду читать ветку целиком)
получить за это потерю производительности,
это с каких пор доступ в линейную память стал аффектить производительность?
вырождается код при таких "разумных" требованиях языка.
тут было бы неплохо какие-то примеры привести ибо за всё время не сталкивался с подобными проблемами.
То есть переход на раст даёт невозможность писать интуитивно понятный код,
эта фраза вызывает ещё большее недоумение. Более интуитивного кода чем на расте не встречал в иных языках. JS в этом плане довольно похож, только в сотни раз медленее. Даже у питона больше квирков в этом плане. Тут похоже тоже нужны какие-то примеры. Пока у вас в коде не возникает необходимость теребить сырые указатели оно даже от C++ отличаться практически не будет, а это требуется на крайне специфичных задачах - едва ли вам каждый день требуется разворачивать односвязные списки.
это с каких пор доступ в линейную память стал аффектить производительность?
С тех, когда этот доступ принудительно проверяется на выход индекса за границу.
Вот на всякий случай ссылка на то, как избегать лишних проверок выхода за границу массива в Rust. Не думайте, что все просто "забыли" или "не знали", что такие проверки могут вредить производительности. Просто они решаются другими способами и без возможности добавить уязвимость в том месте, где производительность (и цена этих проверок) не важна.
Ага, только вот именно при хранении индексов вместо указателей эти способы и не сработают.
Не помогут эти, помогут другие. Да, не получится (точнее я не знаю как) как-то хитро доказать компилятору то, что какие-то числа из рядом лежащего словаря вот 100% в границах массива. У меня в коде в части про BFS не просто так assert!(curr_node_idx < self.nodes.len());
стоит. Этой проверки хватит для того, чтобы больше обращения по curr_node_idx
не проверялись, т.к. он внутри цикла не меняется. А уж одна проверка на большой цикл это не так страшно, там и так куча условий, которые, скорее всего, сломают автовекторизацию.
Оно будет стоить ровно столько же сколько и проверка указателя на выходную ноду ( node->next
ли, node->left/right
ли) на null
. Только в случае с линейной памятью компилятор вполне может сделать предположение о попадании в границы и выкинуть проверку, либо можно самому использовать unchecked версию. Ну и в C++ вполне существуют такие штуки как flat_map
, которые для пользователя выглядят как std::map
, но сами ноды при этом лежат последовательно в линейной памяти, снижая фрагментацию и как следствие уменьшая накладные расходы на выделение памяти.
Hidden text
use indextree::*;
use rand::Rng;
struct Foo {
_p: [u8; 256],
}
fn main() {
let mut linked_list = std::collections::LinkedList::new();
let mut arena = Arena::new();
let query = (0..1_000_000).map(|_| rand::thread_rng().gen_range(0..1_000usize)).collect::<Vec<_>>();
for _ in 0..1_000_000 {
linked_list.push_back(Foo { _p: [0; 256] });
}
let mut last_node = arena.new_node(Foo { _p: [0; 256] });
let first_node = last_node;
for _ in 0..(1_000_000 - 1) {
let node = arena.new_node(Foo { _p: [0; 256] });
node.append(last_node, &mut arena);
last_node = node;
}
let time = std::time::Instant::now();
for i in &query {
let _ = std::hint::black_box(linked_list.iter().skip(*i).next());
}
println!("Linked list: {:?}", time.elapsed());
let time = std::time::Instant::now();
for i in &query {
let _ = std::hint::black_box(first_node.descendants(&arena).skip(*i).next());
}
println!("Indextree: {:?}", time.elapsed());
}
Linked list: 589.368393ms
Indextree: 1.937983ms
Зато прыгать по указателям быстро 😁
Ну вот вам пример из стандартной библиотеки, объясните в чём "интиутивность" этого кода и почему используется Option<NonNull вместо T*
![](https://webcf.waybackmachine.org/web/20240403173053im_/https://habrastorage.org/getpro/habr/upload_files/175/54e/701/17554e7014520d9665274b7d23842e07.png)
Хотелось бы увидеть какой-нибудь практический пример.
Например, объект ‘from', который владеет контролами, списками групп навигации, аксессибилити метками. Контрол-список владеет несколькими элементами - тоже контролами, которые ссылаются на узлы модели данных. Формы ссылаются на сфокусированные контролы, а метки хранят ссылоки на контролы. Между моделью и элементами формы работает подписка на изменения.
Как эти структуры данных выражаются в виде умных ссылок раста. Как lifetimes и borrow checker предохраняют от случайного шарения контрола между списками, включения группового контрола в самого себя, случайного включения одной и той же метки аксессибилити два раза в форму, других нарушений логической структуры.
Как в Расте предлагается обрабатывать эту структуру данных многопоточно?
Собственно, вариантов несколько, и хороших среди них нет.
Встречный вопрос: а как это сделать на Си или на плюсах? Очевидный ответ (везде хранить указатели на контролы) не совсем правильный, потому что приводит к потенциальным use after free сценариями (собственно, поэтому на Расте это не сделать без ансейф).
Я бы сделал список или словарь контролов отдельно, и везде в дереве, подписках и accessibility метках хранил бы идентификатор контрола.
В языке программирования Аргентум это делается тривиально: владеющие ссылки из формы к контролам и из списка к элементам, перекрестные не владеющие между контролами и из модели к контролам и все работает само. Поэтому мне хотелось узнать у любителей Раста, как это делается у них. Получается, никак. Тогда непонятен хайп.
Год назад вернулся на системные языки программирования после 16 лет перерыва, выбирал между Rust и C++. За плюсы у меня в первую очередь было то, что я на них до ухода в дотнет как раз и программировал. Очень заманчиво и логично было бы начать новый проект на Расте, но поискав, я понял что плюсы для проекта больше подходят(стоп, не кидайтесь помидорами, не спешите). Плюсы уже подзабыл и начало было очень болезненным, особенно после 16 лет на комфортном дотнете. Снова подумал взять таки Rust и не мучаться. Но нет, почитал, поискал опять, и купил 9 книг по плюсам и околоплюсовым темам, метапрограммированию, многопоточному программированию, структурам данных, алгоритмам на плюсах, cmake, best practices и т.д.. Шесть уже прочел, осталось три,но самое тяжелое позади, теперь начинается кайф. У меня всего год заняло нормально погрузиться в язык и экосистему, все таки многолетний опыт, в том числе с самими плюсами, очень помогает, но все ж таки год чтения толстенных технических книг не каждому по нраву, понимаю, многим хочется здесь и сейчас, просто творить и не особо сушить мозги. Однако сейчас я на Раст уже не перейду, потому что C++ - это мощнейший инструмент, даже если Rust - тоже неплохой инструмент. Некоторых больших фич, которые есть у плюсов я в Расте просто не нашел. Вот о них и хочу спросить, может я что-то не так понял, поправьте меня.
Метапрограммирование. Я так понял, в Расте есть макросы для генерации шаблонного кода, но таких вещей как спецификация темплейтов(для классов), концепты, SFINAE(это хак, но такой полезный, по сути - спецификация темплейтов для функций). Еще много чего, это большая, сложная тема - и это 1/3 языка С++. Однако это самя мощная фича плюсов, не просто так ведь вся стандартная библиотека на темплейтах и написана. Эта часть действительно очень сильно выделяет плюсы среди других языков, которые все примерно об одном и том же(как и остальные 2/3 самих плюсов), где-то чуть лучше, где-то чуть хуже, но таких мощных темплейтов нет почти нигде, кроме еще пары небольших языков, которые действительно маленькие и вряд ли "выстрелят"(Zig, например).
Все аргументы в спорах сводятся в основном к памяти. Но как я уже упомянул, треть мощи С++ - это темплейты, этой трети вообще нет в Расте, увы. Ну и по-поводу памяти, безопасность-то безопасностью, ну а как в Расте со спецификацией аллокаторов памяти? Это очень небезопасно вообще-то, но это дает мощнейший буст по производительности практически в любой системе, потому что аллокаторы по умолчанию - ни рыба, ни мясо.
Далее, очень важный аспект современного софта - многопоточность. Как в Расте вообще с атомарными данными/операциями(иногда называются volatile), как там сделано разделение на release/acquire/relaxed операции? Кстати, тут особо важно иметь возможность кастомной аллокации, потому что лок-фри структуры данных в этом часто нуждаются, а это ведь не безопасно, значит думать придется.
Еще я несовсем понял часто приводимый здесь аргумент про индекс массива и контроль языка над тем чтобы не выйти за рамки массива. А как тогда индексы проверяются в рантайме на динамических данных? Путем if(i >= 0 && i <= count) ? Если да то, извините, но это защита стоит процессорного времени, причем при каждом обращении к массиву. Если нет, тогда в чем заключается безопасность? Вы точно так же можете поломать данные, переполнить буфер(да, тоже интересный момент, а в Расте что, нельзя уйти в stackoverflow при рекурсии на динамических данных? Конечно можно, и дело тут не в Расте, а в архитектуре машин). Звучит это все для меня как обычный дешевый маркетинг, с этой вашей "безопасностью". Я за 16 лет дотнета видел уже не раз как умельцы хоть на самом безопасном языке делают такие дырявые, протекающие и падающие сервисы, что дубу даешься. А все потому - что мозги отключены, безопасно ведь, зачем думать.
Еще не раз тут видел очень важный пункт от автора статьи, что плюсы компилируются с дата рейсами. Я так понимаю в расте не развиты атомики(volatile), и я сильно сомневаюсь что компиляторы раста имеют хорошую поддержку атомиков для разных платформ. Поправьте, если что, интересно как там дела с этим. Если Раст определяет все нестыковки при работе с данными на всех архитектурах при несинхронизированных атомарных операциях, то извините, Раст это не просто лучший язык программирования, это что-то не от мира сего, на голову выше плюсов. Но что-то я сомневаюсь. Но вообще, есть статические анализаторы. И если уже взялись многопоточный код писать, то лучше уж мозги включить, чем на компилятор полагаться, а то накодите.
И самая важная фича С++ это - специалисты. Это не хайп-платформа, которая предназначена решить все проблемы мира и сразу, это очень глубоко развитая экосистема, над которой все еще работает множество специалистов высочайшего уровня. Да, меньше маркетинга, меньше хайпа, но ни С, ни С++ в этом особо и не нуждаются.
Отвечу на что смогу:
насколько я помню, все коллекции в Rust - дженерики относительно аллокатора, так что его можно менять как для отдельной коллекции, так и глобально;
в Rust атомики, насколько я понимаю, +- скопированны с плюсов, точно так же можно задавать Ordering;
ну да, +-
if(i >= 0 && i <= vec.len())
. Только это нужно только для доступа по индексу, что благодаря очень хорошо развитым итераторам не нужно. А в итераторах все проверки уже сделаны, при итерировании их уже не будет;не знаю, читали ли вы статью до или после того, как я дополнил часть про "Fearless concurrency", там я более подробно расписал гарантии Rust в плане многопоточности. Вкратце, в safe Rust невозможны гонки данных (не путать с состоянием гонки), что, по моему мнению, упрощает написание многопоточного кода в разы;
Rust это уже тоже давно не про хайп. Он уже есть в Linux, он используется в Google, Facebook, Cloudflare и т.д. в тех местах, где ни C, ни C++ не справляются.
Насколько Rust "не просто лучший язык программирования, это что-то не от мира сего, на голову выше плюсов" решать вам. Я уже давно для себя решил, что Rust - это язык, который очень многие вещи делает правильно. И это делает его как минимум одним из самых лучших языков (и точно лучшим из тех, что я знаю).
Rust это уже тоже давно не про хайп. Он уже есть в Linux, он используется в Google, Facebook, Cloudflare и т.д. в тех местах, где ни C, ни C++ не справляются.
смешно, как определили что "не справляется"? Это же и есть хайп, когда пытаются везде сунуть хотя бы немного, а потом делают из этого статьи и рекламу. Никто не делает рекламу из того что гугл каждый день запускает несколько проектов на С++
Думаете все эти компании контролирует некая тайная клика фанатов раста, которая "ради хайпа" заставляет запихать то тут, то там проект на расте? Что значит "делают из этого статьи и рекламу"? Делают рекламу чему? Собственному использованию языка?
Гугл же зачем-то переписал свой сервис с C++ на C++ 3 раза перед тем, как на четвертый переписать на Rust.
А Cloudflare в том числе использует Rust потому, что Nginx слишком медленный:
As Cloudflare has scaled we’ve outgrown NGINX. It was great for many years, but over time its limitations at our scale meant building something new made sense. We could no longer get the performance we needed nor did NGINX have the features we needed for our very complex environment.
это защита стоит процессорного времени, причем при каждом обращении к массиву.
Если компилятор сможет убедиться, что код в принципе не может выйти за пределы массива, то рантаймовые проверки будут выкинуты за ненадобностью. Вот целый пост был об этом https://habr.com/ru/companies/otus/articles/718012/
Да, поэтому я упомянул что речь о динамических данных. Там как ни крути, безопасность можно реализовать только за счет рантайм проверок. Если это так, то это уже не zero cost abstraction, поэтому и интересен этот момент. Тут либо ложное чувство безопасности, либо безопасность за определенную цену, я пока не вижу других вариантов.
Ну, от языка это уже не зависит, в сишечках будет то же самое, только сишечный компилятор уже не проконтролирует корректность кода.
Если очень хочется, можно проверить длину динамического массива один раз перед работой с ним, тогда, как описано в том посте, компилятор скорее всего сможет понять, что проверка уже выполнена, и свои проверки добавлять не будет.
Если очень-очень хочется, можно использовать unsafe-методы для обращения к массиву без проверки — только вот скорее всего не нужно
концепты, SFINAE
Чисто из интереса, зачем вам нужен SFINAE, если вам доступны концепты? Есть какие-то сценарии, которые концептами не покрываются?
Согласен, там масса других фич, которые можно было бы упомянуть. По-сути концепты исключают SFINAE и делают темплейты гораздо читабильнее плюс ускоряют компиляцию и читабельность ошибок инстанциации темплейтов(все мы знаем, какая это была боль до концептов). Я пока не нашел случаев, когда мне пришлось бы применить SFINAE вместо концептов, разве что на старых проектах, но это не мой случай
Так как интересовался, я отвечу.
Аллокаторы
В некотором "неконечном" виде, при этом как будут внедрять "конечный" вид аллокаторов непонятно, т.к. это потребует смены апи, аби и прочих интерфейсов
В стандартной библиотеке аллокаторы поддержаны лишь там, где это позволил сделать язык, например на расте невыразимо rebind_alloc, поэтому мапы (например хеш таблица) не поддерживают аллокаторы
https://doc.rust-lang.org/src/std/collections/hash/map.rs.html#213-215
Модель памяти (и работы с атомиками соответственно)
Полностью скопирована с С++, но заявляется, что "придумают лучше и внедрят", как можно внедрить позже целую модель памяти - я если честно не представляю. Но вот сами атомики, это уже нечто странное. Если в С++ это шаблон, то в расте на разных архитектурах есть разные атомики, все они вручную написаны т.е. AtomicInt32 AtomicInt64 и так далее, а если атомик не является локфри, то его на платформе не будет.
Более того, есть такие прецеденты типа под линуксом у AtomicInt32 будет функция, а под windows не будет или будет у другого типа, честно говоря это как-то абсурдно
Проверка индексов в массиве
Да, просто проверяется на рантайме и бросает местное исключение. Т.е. то что в С++ можно включить флагом компилятора и работает в дебаге само. Насчёт рекурсии аналогично, бесконечная рекурсия == сегфолт в safe расте. Они пытаются это решить требуя от платформ (!!) вставлять в конец стека guard pages, но это само по себе смешно, язык требует от микроконтроллеров как то себя вести
Невозможность гонок данных
Организуется через форсирование копирования, излишних синхронизаций (шаред поинтеры, мьютексы), любой "многопоточный" код в safe вырождается либо в использование готовых функций, либо в очень неэффективный код, при этом dead lock/live lock и утечка памяти это всё ещё 'safe' код, т.е. могут произойти (я уж промолчу про то, что unsafe блоки в зависимостях могут и содержат ошибки)
Спасибо, снимает некоторые вопросы, о том как Раст работает. Я пока не зарывался в эту тему, но делаю периодически селф-чеки, чтобы понимать, на правильном ли я пути.
Еще вопрос, возможно ли в Расте получить болтающиеся указатели? Например передаем ссылку на переменную со стека в лямбду(или какой у Раста аналог лямбд?), лямбду передаем куда-то еще, удаляем фрейм со стека, вызываем лямбду и вуаля. В принципе такое легко покрывается статическими анализаторами, да и просто культурой разработки, но неужели в Расте такого не бывает? вижу три варианта: там встроенный статический анализатор, там все создается на стеке, там нельзя передать куда-либо ссылки на переменные со стэка. Резонный вариант был бы - статический анализатор, но такое и в плюсах, и в С есть.
По-поводу "невозможности" гонок данных. Примерно как я и подозревал, вся инженерия спрятана за красивым фасадом с табличкой "не влезай убьет" и сделана максимально безопасно получается(синхронизация кешей процессоров и основной памяти). Тогда как же использовать преимущества архитектур, где можно кучу времени сэкономить на несинхронных операциях?
С типами данных под каждый атомик - беда. Какая все таки мощь плюсовые темплейты. Они же - ужас, боль и страдание для новичков.
"там все создается на стеке " - я имел в виду на куче, извиняюсь
Уже раз наверно десятый я рекомендую цикл лекций Алексея Кладова. 13 лекций по полтора часа скорее всего ответят на все ваши вопросы и даже больше.
передаем ссылку на переменную со стека в лямбду
Не скомпилится, компилятор заявит, что значение не живёт достаточно долго
На расте писать так же занимательно, как продираться сквозь спойлеры в вашей статье :)
А если серьезно, спасибо за ряд любопытных ссылок и источников — я как раз из разряда тех, которые:
Я просто иногда пишу на нем разные вещи для развлечения и смотрю умные лекции на ютубе в надежде стать умнее
поэтому некотрые материалы схоронил себе на изучение
Не могу не добавить некоторые пункты про Раст, которые не позволяют ему заменить Си (пока, надеюсь, что со временем исправят). Конечно, надо отметить, что сам стандарт Си не все покрывает, что-то есть в C++, но тоже не все, и некоторые вещи работают только с gcc/clang extension:
отсутствие likely в stable (https://internals.rust-lang.org/t/could-unlikely-and-likely-be-stabilized-in-std-hint/9795?u=lzutao). Уже пять лет, а воз и ныне там. Официальный способ - #[cold], но это не покрывыет все потребности. Нужно для избежания branch misprediction.
alignment на уровне полей структур. В принципе не критично, т.к. можно разбить на подструктуры, но это может поломать совместимость с Си. Нужно для эффективного использования CPU cacheline.
Нефиксированный порядок полей в структурах. Можно считать как плюсом, так и минусом (Rust может более эффективно упаковать поля в структуре), но для высокоэффективного кода приходится применять соответствующие аттрибуты, чтобы это отключить. Опять-таки не критично, но нужно знать.
Отсутствие некоторых высокопроизводительных функций в std, например rint (https://en.cppreference.com/w/c/numeric/math/rint). С другой стороны, `
to_int_unchecked
` использует LLVM’s fptoui/fptosi (https://doc.rust-lang.org/std/intrinsics/fn.float_to_int_unchecked.html), так что это может быть не проблема.
Опять-таки, это нужно для очень высокопроизводительного кода, когда идет счет на такты процессора, для абсолютного большинства программистов это не нужно. И Раст в этом плане не так уж плох, тем более есть использовать nightly, в котором есть все что надо.
Вопрос библиотек тоже стоит, например, использовать uring_io несколько проблематично. tokio_rs добавляет кучу абстракций, которые реально замедляют код (опять-таки, для большинства будет нормально, я ссылаюсь на ситуации когда нужны гигабиты в секунду). moniui более или менее нормально, но сыроват. А напрямую binding-и писать занимает время.
отсутствие likely в stable
Для каких задач это прямо важно? Современные процессоры такие чудеса с предсказаниями творят, что диву даёшься (амд вон псевдорандомные ветки предсказывать умеет), и я думал, что эти хинты процессоры всё равно игнорируют. И разве это PGO/BOLT не позволяют подкрутить уже после компиляции, по результатам профайла реальной работы?
Я пишу код больше 20 лет, разрабатываю сейчас cloud application на Go, поклонник unix way и не могу придумать зачем мне, как тех лиду, нужен rust на проектах.
Если говорить про работу в команде и синтаксис языка, то в идеале "код должен быть таким что бы смотря на него не было ясно кто его написал", на rust можно написать один и тот же код, кучей разных способов, как по мне это минус.
Если говорить про коммерческую разработку, тут важна не только скорость разработки, но и количество людей доступных на рынке и сложность поддержки проекта. Скорость разработки в том числе зависит от количества надежных библиотек вокруг языка.
На момент написания комментария на хабр карьере 5 вакансий rust разработчика и 197 вакансий на go разработчика.
Немного про async IO
node.js и Go из коробки поддерживают асинхронный ввод вывод (epoll, IOCP) надеюсь скоро в Go включат поддержку io_uring. Причем сам разработчик об этом и не знает, все происходит нативно. В rust же нужно использовать стороннюю библиотеку tokio.
Go из коробки имеет "горутины", которые автоматически масштабируются по количеству доступных процессоров. Если кто-то здесь изучал "параллельное программирование" тому известно что такое "переключение контекста" на уровне ОС и сколько это стоит. В rust насколько я понял используются системные потоки, со всеми вытекающими, а асинхронность Future однопоточная? Ну и немного цитаты из документации "Migrating from threads to async or vice versa typically requires major refactoring work"
Немного про shift-left-security
Мы в "компаниях" хотим безопасное по, для этого нам нужен SAST, DAST, SCA. SCA для go это govulncheck
от самих создателей языка, SAST golangci-lint причем многие утилиты от самих создателей языка. Есть ли в rust подобная экосистема?
Закон Парето гласит, что во многих случаях 20% усилий дают 80% результата и я не вижу, где я могу применить rust в своей работе, что бы это было оправдано чем-то кроме "а давайте всё перепишем на rust". Надеюсь вы сможете меня переубедить :)
Rust — это не «memory safe C»