Команда Rust рада сообщить о выпуске новой версии — Rust 1.54.0. Rust — это язык программирования, позволяющий каждому создавать надёжное и эффективное программное обеспечение.
Если у вас установлена предыдущая версия Rust через rustup, то обновиться на версию языка Rust 1.54.0 все так же просто, как никогда:
rustup update stable
Если вы всё ещё не используете rustup
, вы можете установить rustup
на соответствующей странице нашего веб-сайта и ознакомиться с подробными примечаниями к выпуску 1.54.0 на GitHub.
Что стабилизировано в 1.54.0
Этот выпуск содержит несколько новых возможностей языка.
- Добавлены новые сценарии использования макросов
- Стабилизированы компиляторные вставки на платформе wasm32
- Инкрементальная компиляция по умолчанию
Атрибуты могут вызывать макросы подобные функциям
Rust 1.54 поддерживает вызов макросов подобных функциям внутри атрибутов. Такие макросы могут быть основаны на macro_rules!
или быть процедурными макросами, которые вызываются как macro!(...)
. Одним из примечательных вариантов использования этого является включение документации из других файлов в комментарии к документации Rust. Например, если README вашего проекта представляет собой хороший комментарий к документации, вы можете использовать include_str!
для непосредственного включения содержимого. Раньше различные обходные пути допускали аналогичную функциональность, но с версии 1.54 это стало намного эргономичнее.
#![doc = include_str!("README.md")]
Макросы также могут быть вложены в атрибут, например, для включения содержимого, созданного сценарием сборки:
#[path = concat!(env!("OUT_DIR"), "/generated.rs")]
mod generated;
Подробнее читайте здесь.
Стабилизация компиляторных вставок (intrinsics) в wasm32
Стабилизирован ряд компиляторных вставок использующихся на платформе wasm32, что даёт доступ к инструкциям SIMD в WebAssembly.
Примечательно, что, в отличие от ранее стабилизированных x86
и x86_64
, у них нет требований безопасности, которые должны вызываться только при включении соответствующей целевой функциональности. Это связано с тем, что WebAssembly был написан с самого начала для безопасной проверки кода перед его выполнением, поэтому инструкции гарантированно декодируются правильно (или не декодируются вообще).
Это означает, что мы можем предоставить некоторые компиляторные вставки как полностью безопасные, например v128_bitselect
. Однако есть некоторые компиляторные вставки, которые небезопасны, потому что они используют сырые указатели, например v128_load
.
Инкрементальная компиляция снова включена по умолчанию
Инкрементальная компиляция была повторно включена по умолчанию в этом выпуске после того, как она была отключена по умолчанию в 1.52.1.
В Rust 1.52 была добавлена дополнительная проверка при загрузке данных инкрементной компиляции из кеша на диске. Это привело к обнаружению ряда ранее существовавших потенциальных проблем с надёжностью, поскольку проверка изменила эти скрытые ошибки на внутренние ошибки компилятора (ICE). В ответ команда разработки компилятора решила отключить инкрементную компиляцию в исправлении 1.52.1, что позволило пользователям избежать столкновения с ICE и лежащей в их основе несостоятельностью за счёт более длительного времени компиляции. [^1]
С тех пор мы провели серию ретроспектив, и участники усердно работали над устранением обнаруженных проблем, при этом некоторые исправления были внесены в 1.53, а большинство — в этом выпуске. [^2]
В настоящее время есть ещё две известные проблемы, которые могут привести к ICE. Из-за отсутствия автоматических отчётов о сбоях мы не можем быть полностью уверены какое влияние они оказывают. Однако на основании отзывов, полученных от пользователей, затронутых выпуском 1.52, мы считаем, что оставшиеся проблемы на практике возникают редко.
Поэтому в этом выпуске снова включена инкрементная компиляция!
[^1]: [примечания к выпуску 1.52.1] содержат более подробное описание этих событий.
[^2]: Задача для отслеживания: #84970.
Стабилизированные API
Стабилизированы следующие методы и реализации типажей.
BTreeMap::into_keys
BTreeMap::into_values
HashMap::into_keys
HashMap::into_values
arch::wasm32
VecDeque::binary_search
VecDeque::binary_search_by
VecDeque::binary_search_by_key
VecDeque::partition_point
Прочие изменения
В выпуске Rust 1.54.0 есть и другие изменения: узнайте, что изменилось в Rust, Cargo и Clippy.
Также был исправлен rustfmt в версии 1.54.0 для правильного форматирования вложенных автономных модулей. Это может вызвать изменения в форматировании файлов, которые игнорировались 1.53.0 rustfmt. Подробности смотрите здесь.
Участники сделавшие вклад в версию 1.54.0
Многие люди собрались вместе, чтобы создать Rust 1.54.0. Без вас мы бы не справились. Спасибо!
От переводчиков
С любыми вопросами по языку Rust вам смогут помочь в русскоязычном Телеграм-чате или же в аналогичном чате для новичковых вопросов. Если у вас есть вопросы по переводам или хотите помогать с ними, то обращайтесь в чат переводчиков.
Также можете поддержать нас на OpenCollective.
Данную статью совместными усилиями перевели andreevlex, TelegaOvoshey, blandger, Belanchuk и funkill.
Комментарии (112)
picul
03.08.2021 15:54+1Ну Вы на святую святых посягнули, в подобных ситуациях можно и еще более весомые аргументы услышать :)
technic93
03.08.2021 16:41+1Таким аргументам не место на Хабре. У нас технический ресурс, надо отвечать по сути вопроса или молчать.
AnthonyMikh
03.08.2021 17:14+1Я слишком хорошо знаю, что это за персонаж. Это далеко не первый его аккаунт тут. Агрессивен, малообразован, превозносит C++, поносит Rust, уровень технической аргументации ниже плинтуса, постоянно пытается продвинуть идею о том, что популярность Rust обусловлена не техническими причинами, а тех, кто с ним не согласен, записывает в мифических "адептов".
Вот увидите, скоро опять начнутся и "запартные поломои", и "школьники", и "макаки", и истерика про ворованный компилятор.
KanuTaH
03.08.2021 18:15+1Агрессивен? Скажем так - не более, чем иные раст-адепты здесь. Одному крабообразному из вашей тусовки здесь недавно тоже крышу сорвало на почве неудобных вопросов, пришлось в readonly отправлять. Малообразован? Не сказал бы. Малообразованными выглядят скорее некоторые адепты раста, у которых в активе ничего, кроме лозунгов. Превозносит С++, поносит раст? Вы занимаетесь тем же самым, только у вас языки местами переставлены - но "это другое". Никакой популярности у раста нет, есть хайп, а мнение этого гражданина о пользе хайпа для бизнеса (из коей, по его мнению, растут ноги расто-пиара про "поддержку крупных игроков") вполне имеет право на существование. Не надо здесь угроз типа "доиграешься, придут активисты и накажут", это не тот ресурс.
picul
03.08.2021 18:38+2Вы уж меня извините, но при всей грубоватости повествования этого "персонажа", Вы тоже часто отличаетесь резкими заявлениями и фактически никогда - нормальной их аргументацией. Так что если кому и обвинять в голословности, то точно не Вам; а тон, которым Вы это сделали, я бы попросил вообще исключить из арсенала, хотя бы здесь.
WASD1
30.07.2021 20:45Коллеги здравствуйте. Хочу попробовать Rust (на работе). Но проблема в том, что у нас закрытая сеть, машины разработчиков под Ubuntu. Из внешней сети пробовать работать не вариант - совсем игрушечные проекты делать не хочется, pet-project на данный момент нет.
Так вот подскажите как настроить в такой ситуации работу с Rust (ну или хотя бы в какую сторону гуглить для начала)?
Может быть делают *deb пакеты каких-то стабильных выпусков (а что со сторонними библиотеками тогда и как их обновлять)? Или ещё какой механизм?amishaa
30.07.2021 22:30+1rustc (и stdlib к нему) есть просто в репозитории: http://manpages.ubuntu.com/manpages/bionic/man1/rustc.1.html (в 18.04 сильно не последней версии, начиная с 20.04, вроде бы, уже достаточно актуальный).
cargo (систему сборки) можно заставить работать в offline mode: https://stackoverflow.com/questions/57336771/how-do-i-use-cargo-to-build-a-project-while-offline#57337200WASD1
30.07.2021 22:46Спасибо.
А получить локальный репозиторий внешних пакетов (выкачать всё, упомянутое в crates.io, желательно ещё и взаимно совместимых версий (по формальным признакам версий пакетов, транзитивно вниз по дереву зависимостей)) как-то можно?
Может быть cargo (или какая-то другая внешняя утилита) уже делает такие оффлайн-репозитории всех пакетов?amishaa
30.07.2021 22:59+1Беглый поиск по github нашёл cargo-mirror, написанный на питоне.
Но для конкретного пакета (или списка пакетов) достаточно cargo fetch: https://doc.rust-lang.org/cargo/commands/cargo-fetch.html
DarkEld3r
30.07.2021 23:14выкачать всё, упомянутое в crates.io
Прям вообще всё с crates.io? Тут не подскажу, но если понятно какие зависимости нужны, то есть cargo vendor. У нас на проекте именно оно и используется. То есть, добавление или обновление зависимостей проходит через определённую процедуру, код зависимостей выкачивается при помощи этой команды и дальше для сборки интернет не нужен.
dllmain
Всё же нет, к сожалению. Во первых, в коде на Rust слишком много unsafe, начиная со стандартной библиотеки. Безопасность здесь так же лежит на плечах программиста.
Во вторых, причина большого количества unsafe - невозможность существования одновременно эффективного и безопасного кода. То есть, правильной формулировкой было бы "позволяющий каждому создавать надёжное ИЛИ эффективное программное обеспечение".
humbug
Все же нет. Причина наличия unsafe - это наличие другого семантического ядра, в котором компилятор не может самостоятельно проверить условия. Почитайте диссертацию Ральфа Юнга - https://people.mpi-sws.org/~jung/thesis.html.
Кстати, в ней же он доказывает возможность создания безопасного кода на костях unsafe.
dllmain
Вы хотели сказать наличие конструкций, которые невозможно выразить на safe Rust(иначе другая семантика просто не нужна). Тот же двусвязный список, например. Я не учитывал эти случаи несмотря на их многочисленность, потому как посчитал это очевидным.
Ну это не имеет значения. Возможность есть для любого языка.
defuz
Абсолютно любой код на любом языке программирования в конечном итоге компилируется в небезопасный код, работающий напрямую с памятью и устройствами, просто потому что так устроена архитектура компьютеров.
Rust – это на самом деле два языка программирования (Safe Rust и Unsafe Rust), причем Safe Rust является подмножеством Unsafe Rust. Эта особенность позволяет разработчикам создавать надежные И эффективные абстракции для небезопасных по свое природе операций, а затем безопасно И эффективно использовать их в прикладном коде.
Так что да, Rust позволяет создавать надежное И эффективное ПО.
leechong
Если всё так как вы говорите, то Rust отличается от C или C++ только тем, что он новый и с самого начала приветствует использование только "библиотек", не поощряя использование основного языка. Ну и "библиотеки" направлены в первую очередь на безопасность, во вторую на производительность.
defuz
Библиотеки, в том числе стандартная библиотека языка, направлены на безопасность без компромиссов с производительностью, это основная парадигма Rust, как бы.
Если Rust ничем не отличается от C/C++, то почему в нем реализованы отдельно ссылки и указатели? Не стоит так безапеляционно заявлять о том, в чем вы не разобрались. Очевидно, в C/C++ не заложен тот инструментарий контроля за безопасностью, который реализован в Rust на уровне языка.
Безопасный код на Rust может упасть с segmentation fault если в нижележащем низкоуровневом коде была допущена ошибка, как и абсолютно любой другой "безопасный" код написанный в другой парадигме.
Rust предлагает новую парадигму построения абстракций между Safe и Unsafe, потому что в основе любой безопасной реализации будут лежать небезопасные примитивы, корректность использования которых лежит на разработчике. Основная идея Rust – локализовать применение небезопасных примитивов, обернув их в безопасную абстракцию. И это работает.
leechong
Ваш второй комментарий противоречит первому. "Вы либо крестик снимите, либо трусы наденьте." Мой комментарий основан исключительно на вашем первом комментарии. Я на Rust написал только "hello world".
defuz
Не понимаю где вы увидели противоречие, так что наверное не смогу точно ответить.
Unsafe Rust действительно по уровню гарантий и поведению близок к C/C++, но в нем, к счастью, очень короткий список undefined behaviour.
Safe Rust по уровню гарантий похож скорее на Java, но не является managed языком: ни виртуальной машины, ни сборщика мусора там нет.
Писать на Rust – это примерно как писать на C низкоуровневые библиотеки, и затем использовать их на Java в основном коде, с той лишь разницей, что синтаксически Safe/Unsafe Rust – почти идентичные языки, которые бесшовно интегрируются, без каких-либо накладных расходов.
При этом безопасная часть кода не будет уступать по производительности аналогичной программе на С/С++, хоть и написана на "безопасном языке".
В действительности, 90% Rust-разработчиков никогда не пишут на Unsafe Rust, но весь их код так или иначе основан на Unsafe Rust. Стандартная библиотека языка – это по сути сплошной unsafe-код, завернутый в абстракции, корректное использование которых гарантируется компилятором.
technic93
Список там как в Си и плюс ещё вагон связанный с алиаснигом.
К сожалению будет, потому что добавились проверки выхода за границу массива и какие-то RefCell. И далеко не во всех случаях их оптимизатор ллвм убирает.
Это хорошо, но стандартной библиотеки мало, и придумать как что то засунуть в ограничения сейф раста не просто.
AnthonyMikh
Царь, ты
заенадоел срать на Хабре. С нетерпением жду, когда из тебя с неизбежностью вновь попрёт шизофазия и тебе справедливо сольют карму.dllmain
Тут вы снова заблуждаетесь. Создание безопасных абстракций поддерживается большинством(всеми?) языками программирования. Никакой новой парадигмы здесь нет.
defuz
Уточняю: создание абстракций, безопасное использование которых гарантируется компилятором.
dllmain
Ну оно не гарантируется - никто же не мешает писать unsafe в клиентском коде.
amishaa
Например, деректива
#![forbid(unsafe_code)]
запрещает писать unsafe в крейте.dllmain
Хорошо, правда это не совсем "гарантируется компилятором", это "компилятор может помочь отследить использование unsafe". Даже по умолчанию unsafe не запрещён. Но ладно, принимается.
Итого по данной ветке: Rust позволяет более явно разделять safe и unsafe(с возможностью запрещать последний локально). То есть просто интеграция части инструментария в компилятор. Здесь нигде нет даже предпосылок для надёжного и эффективного ПО, и уж тем более новой парадигмы.
amishaa
Компилятор гарантирует, что если stdlib написан корректно, то safe код, написанный поверх stdlib, не приведёт, например, к разименованию нулевого указателя.
Да, предположение о корректности является довольно сильным. Но если стандартная библиотека содержит серьёзные ошибки, то на любом языке программирования у Вас проблемы.
amishaa
ПО пишут люди. Даже в стандартной библиотеке и компиляторе любого из современных языков регулярно находят ошибки. Поэтому писать "если совсем нет ошибок, то ..." - это слишком сильное утверждение.
Для stdlib (и любой другой библиотеки использующей unsafe) сам компилятор корректность гарантировать не может. Так же как с C# unsafe - можно выстрелить себе в ногу в unsafe блоке, а упадёт само приложение где-то дальше.
Но никто не мешает написать быструю библиотеку для чего-то своего поверх stdlib. Как раз в силу того, что абстракции дешёвые во время исполнения (и компиляции) она будет достаточно близка к low-level.
Да, если нужна работа с железом (на уровне регистров, например), придётся писать какой-то unsafe код (или пользоваться другой библиотекой, которая предоставляет безопасную абстракцию поверх этих регистром).
vtb_k
Может вы приведете хоть какие-то доказательства таким голословным утверждениям? Графики, бенчи, исследования?
amishaa
Утверждение "Компилятор гарантирует, что если stdlib написан корректно, то safe код, написанный поверх stdlib, не приведёт, например, к разименованию нулевого указателя." абсолютно прозрачно меняется, если поменять stdlib на другой источник unsafe кода.
"Компилятор гарантирует, что если весь unsafe код (включая код зависимостей) написан корректно, то safe программа не вызовет разыменование нулевого указателя".
amishaa
Почему именно fakestatic, а не ещё один из примерно 70 unsound багов?
amishaa
Это, очевидно, баг в подсчёте ограничений в where clousre. Вот иллюстрация той же самой проблемы, без использования 'static: https://github.com/rust-lang/rust/issues/84591.
Если бы не two compelemnt был бы багом, то все оптимизации компилятора, которые завязаны на не-переполнение signed int, были бы багом. Вообще, "переполнение знаковых - это undefined behaviour" - это фича языка, а не бага - оптимизировать код, в котором программист задумался и избежал можно лучше (и компиляторы это делают).
Другое дело, что на результат работы программиста это накладывает дополнительные ограничения.
amishaa
А почему не починен этот пятилетний баг питона? Потому что про баг решили, что он не критичный (в том смысле, что проявляется довольно редко и случайно его вызвать довольно сложно).
Нет. В стандарте C++ разделены unspecified и undefined behavior.
Undefined behavior - это именно о том, что компилятор в рамках оптимизации Вашей программы на C++ может предполагать, что Вы (как программист) гарантировали, что оно не случится.
То, что поведение программы начинает зависеть от
фазы луныминорной версии компилятора и уровня оптимизации при сборке? Не говоря о том, что перестановка каких-нибудь гарантированно не-зависимых вызовов может приводить к изменению поведения?Ну нет же. Сегодня компилятор C++ имеет полное право заменить for(int i = 0; i > 0; i++) на while(true) с целью оптимизации (если, конечно, i не используется в теле цикла). А пользуется он этим правом или не пользуется - это его решение (и для сохранения поведения нужно привязываться к gcc/clang одной конкретной минорной версии)
picul
А есть ссылка на источник где об этом написано?
amishaa
Прочитали бы внимательно. Это баг в PyModule_AddIntConstant(), которая часть ядра питона.
Там ещё 70 таких багов. Их постепенно закрывают. Учитывая, что вот этот похожий пометили high, есть довольно хорошие шансы, что наконец пофиксят (оба сразу).
Ага, и поэтому программа с UB может давать разный результат, если скомпилированна с -O0 и -O3? И ключ с -fno-strict-aliasing (с которым нужно собирать ядро линукса) выключен по-умолчанию для причин не связанных с оптимизациями компилятора?
Может. Например, часть некорректного кода компилируется старым компилятором, но не компилируется новым (потому что пофиксили ошибку, которая разрешала некорректный код, с трейтом fake-static случится именно это). Часть корректного кода компилируется новым компилятором, но не компилируется новым (потому что какие-то проверки в safe делают менее жёсткими). Отдельно, код, содержащий некорректный unsafe, может тоже собираться по-разному, даже минорными версиями (как и код содержащий некорректный с++).
И это действие превращает Вас из C++ программиста в программиста на gcc10.2.0 , гарантий, что код написанный вами, скомпилированный 10.2.1 даст тот же результат уже никто не даёт.
Выкидывание стандарта привязывает вас к минорной версии (см. выше или ниже).
Ответил тоже выше - имеет отношение к оптимизациям кода, которые делает компилятор.
Изменение поведения кода, содержащего UB не является ломающим изменением.
amishaa
В тикете есть пример. Берёте внешнюю библиотеку (тоже написанную на безопасном питоне) и получаете неправильный результат работы, если собирать на конкретную архитектуру.
Вы ищете пример кода, который с O2 и с O0 даёт разные результаты на большинстве современных компиляторов?
Так же как с с++ какой-то редакциии есть гарантия, что если код корректен (и не попал на какую-то ошибку компилятора), то смена версии компилятора он продолжит компилироваться в программу, дающий тот же результат. Гарантии, что он скомпилируется в ту же самую программу - нету.
Видимо, мы наблюдаем разные миры. В С++ рядом со мной пишут как раз под 11 стандарт. И подготовка тестовой сборки на clang (для тестов производительности, боевая у нас на gcc) заняла пару дней (для того, чтобы поменять скрипты сборки). После этого сборка прошла все тесты.
dllmain
Да, это два языка, верно. Только вы не приняли во внимание(или приняли, но ошиблись в каких-либо других рассуждениях) что unsafe предоставляет только эффективность, а safe - только надёжность. Очевидно, один и тот же код не может быть сразу и safe, и unsafe. Поэтому никакого "И" здесь нет. Вот так должен был выглядеть отрывок вашего поста, который я процитировал:
defuz
Мне кажется мы уже об определении слов начали спорить, поскольку с вашим вариантом я тоже согласен. Я имел ввиду что создаваемые абстракции безопасные в использовании И эффективные в реализации.
dllmain
Я слабо представляю себе значение фразы "эффективность в реализации", но на мой взгляд в статье совсем не об этом говорилось:
В своём первом комментарии я говорил именно об эффективности при исполнении. Или, проще говоря, эффективность == малый расход ресурсов в runtime, насколько это возможно.
PROgrammer_JARvis
Как раз идея Раста в том, чтобы предоставить дешёвые (в идеале, с нулевой стоимостью) абстракции, которые при этом безопасны.
Достигается это именно путём того, что на низком уровне что-то (и то, относительно узкая часть, а именно "самые-самые" примитивы + интеграции с внешними функциями) реализовано с unsafe, однако предоставляемый интерфейс (в широком смысле) - safe.
Вот простой, но, как мне кажется, достаточно характерный пример, реализация
swap
в стандартной библиотеке:Небезопасный
ptr::swap_nonoverlapping_one
небезопасен по той причине, что требует, потому что (вероятно) производит разыменование сырых указателей, а, главное, требует того, чтобы эти области памяти, на которые они указывают, не пересекались. В то же самое время, borrowing checker не позволяет иметь два&mut T
, ссылающихся на одну и ту же область, поэтому вызов небезопасногоptr::swap_nonoverlapping_one
от них безопасен. Что же до эффективности, то никакого оверхеда здесь нет. Преобразование ссылки к указателю это классический no-op. Инлайн же этой функции точно так же тривиален и почти наверняка (формально, компилятор имеет право решить, что он не нужен, но тут вероятность подобного стремится к нулю) произойдёт.Таким образом мы получили безопасный swap при этом обладающий всеми преимуществами, с точки зрения производительности, безопасного варианта. При этом разработчику, пользующемуся им, не важно, как он устроен под капотом, и при этом он никак не может вызывать эту функцию неправильно.
Идея unsafe не в том, что им помечено всё быстрое и волшебное. Unsafe является то, что ведёт к неопределённому поведению при нарушении инварианта, который компилятор не имеет возможности гарантировать.
Другой неплохой пример -
std::hint::unreachable_unchecked
: он небезопасен, потому что переносит ответственность на доказательство того, что данный блок недостижим, на разработчика, зато даёт право компилятору полагаться на то, что этот блок гарантированно недостижим, например (пусть функцияrand(n)
даёт случайное число от 1 до n):Компилятор не имеет никакой возможности явно доказать, что числа кроме 0 и 1 невозможны, потому что это логика вашей
rand
, но вы можете сделать это, взяв ответственность за ошибку в случае, если это не так, на себя, причём строго в пределах блока unsafe. В то же самое время, если вы "не, уверены" есть и не-unsafe аналог который вставляет код для паники в место своего использования, хоть он и не даёт компилятору просто так вырезать эту ветку. Причём, в действительности, компилятор, быть может, и в состоянии сам понять, чтоrand(2)
не может вернуть ничего, кроме 0 и 1 и в таком случае и вариант с паникой без проблем будет вырезан.Ну и другой пример для того, чтобы показать, что то, что в Расте safe, в целом, сделано наиболее дёшево, и аналогичный код на (например) C++ был бы ни чуть не быстрее: великий и могучий
Option<T>
. Самый близкий аналог на плюсах -std::optional<T>
. И классическая же операция дереференса. Что в расте, что в плюсах, если вы собираетесь достать из него T, вам необходимо проверить, есть ли он там вообще. В C++ для этого используется`operator bool()`
илиvalue()
, который можно банально забыть написать или при рефакторинге забыть изменить. Иначе говоря, проблема подхода там - неатомарность операций:Неатомарность в том значении, что компилятор не сможет никак гарантированно вам помочь, если вы забудете выполнить эту проверку. Может (i.e расширения), но не обязан, потому что с точки зрения языка код без проверки всё так же корректно. А плохо это потому что "быстрый"
operator *
ведёт к UB, если значение отсутствует.Раст же даёт инструментарий (причём, довольно разнообразный), при котором чисто аналогичный код атомарен:
Как видно, код на обоих языках делает абсолютно одно и то же, но то, что в плюсах таит UB, которое может быть никем на замечено, в Расте обёрнуто в безопасную обёртку, которая при этом ничуть не проигрывает по производительности.
dllmain
Это не идея Rust, как я уже говорил. Такая модель известна и используется не менее 30 лет(но скорее всего и того больше). Зачем вы приписываете себе чужие идеи?
Что нового вы здесь сообщили? Такое есть где угодно, даже в том же питоне и прочих(там сишный код, вызываемый из питона == unsafe в Rust). Про языки помощнее типа C++ и говорить не стоит. Снова выдаёте чужое за своё.
Это тривиальная операция, оверхэда здесь быть не может. Кстати, примечательно что даже такой простой код написан полностью в unsafe.
Вполне может, код на Rust невозможен без unsafe, как показывает практика. Примитивные случаи когда уже есть готовое API и осталось только вызвать функции в нужном порядке мы не рассматриваем - здесь любой язык покажет сравнимый уровень безопасности.
Про гарантии и инварианты я писал выше. Unsafe есть потому как safe Rust не может выразить множество конструкций. Многопоточная код как пример - потоки просто не смогут взаимодействовать, потому как safe Rust запрещает разделение данных.
Который снова ничего не иллюстрирует. Что вы хотели показать этим примером?
Уже поближе к теме. На этом примере можно видеть полное отсутствие различий в контексте безопасности между C++ и Rust. Во первых, проверять нужно не всегда, но safe Rust не может не проверять - эффективности уже нет. Пытаемся эту эффективность не терять - пишем unsafe и теряем безопасность. Во вторых, забыть написать .value() возможно также, как и написать unsafe {} в Rust(т. е. это просто из серии "страшилки на ночь"). В ту же кучу доступ к элементу вектора по индексу.
amishaa
Как правильно безопасно реализовывать swap без дополнительной памяти и внешней гарантии на то, что объекты не пересекаются по памяти?
Вопрос в скорости абстракции. Написанная на питоне программа будет (почти всегда) работать существенно медленнее, чем программа, написанная на C++. Rust-абстракции в этом смысле гораздо более быстрые.
Если вы проверили это уже где-то раньше - поменяйте тип с Option<T> на T, после этого проверять не нужно. Если данные приходят в функцию в виде Option<T>, то проверять нужно (даже если сегодня мы знаем, что там всегда что-то есть, то завтра кто-то может попробовать переиспользовать эту функцию по её сигнатуре и передаст туда Empty).
Можно забыть написать unsafe в Rust. Полученный код не скомпилируется. А вот если забыть написать .value() в C++, то полученный код скомпилируется (но на отдельных данных будет вызывать неопределенное поведение).
technic93
Кстати, похожие проверки что есть if на сравнение с null перед использованием есть в тайпскрипте.
technic93
Поэтому if let синтаксис не обязателен для работы с optional.
dllmain
Как интересно вы проигнорировали часть моего сообщения. Спрошу ещё раз: что насчёт идеи о безопасных абстракциях? Почему предыдущий комментатор написал "это идея Rust", хотя она таковой не является?
Например так:
Хотя здесь даже restrict не нужен. Если же вы про Rust - никак. Это ещё один из многих случаев, когда safe Rust не может выразить нужную операцию. Причём здесь нет ничего небезопасного или "инварианта, который компилятор не имеет возможности гарантировать" - операция элементарная.
Зачем вы это рассказываете? Программа на safe Rust также будет работать медленнее программы на C++. Если вы хотели сказать "Rust быстрее Python во многих случаях" - поздравляю, конечно, вы победили питон(наверное). Только этого недостаточно для написания эффективного ПО.
Это нерабочий сценарий, поскольку теряется возможность модифицировать объект в option. Либо избыточные проверки, либо два лишних копирования. И это уже в простейшем случае. С вектором всё ещё хуже.
Постойте, вы сами себе противоречите. Выше был упомянут swap, в который завтра кто-то может попробовать передать ссылки на один и тот же объект через unsafe(и получить то самое UB). Но swap почему-то безопасен, а optional без проверки нет.
Ещё раз: нельзя забыть написать .value() - это так же не скомпилируется, поскольку типы будут разные. Если же вы забываете таким образом, что вместо .value() пишете *(это какая-то очень странная форма забывчивости) - вы точно также и в Rust вместо безопасного кода напишете unsafe.
PROgrammer_JARvis
Не очень понимаю такой зацикленности на моей фразе про идею, учитывая то, что изначально дискуссия была о более прикладной составляющей (в моём понимании), а не том, кто первый что изобрёл (по крайней мере, в этой части обсуждения участвовать не хочется), но, хорошо, соглашусь, выразился неудачно: это не что-то, что изобрёл Раст, это подход, который существует давно, но конкретно в нём позволяет на (практически) одном и том же языке писать что safe, что unsafe код, и при этом safe подмножество не несёт за собой оверхеда, характерного для управляемых языков.
Доказывался другой тезис, в именно то, что производительная низкоуровневая реализация (
swap_nonoverlapping_one
реализован, в общем, подобно тому, что вы отправили) не мешает быть точно такому же эффективному безопасному интерфейсу.Если речь об
std::swap
, то почему вы в этом и предыдущем сообщении говорите о том, что кто-то будет вызывать его из unsafe, если это safe функция? Ещё раз,swap
делегирует к unsafe методуswap_nonoverlapping_one
, который требует соблюдения строго инварианта, но пользователя это не волнует. Потому что на вызовеswap
этот инвариант итак гарантирован. Задача реализацииswap
- вызватьswap_nonoverlapping_one
, потому что два алиасных&mut T
быть не может, а вот для*mut T
такие гарантии должен (для данной задачи) гарантировать разработчик. Но не разработчик клиентского кода, а разработчик реализацииswap
. Я же как разработчик просто пользуюсьswap
из safe и не задумываюсь (утрированно) над тем, как он реализован, потому что я знаю, что вызывать его неправильно я чисто физически не могу.Полагаю, проблема в моей опечатке выше, речь шла о
has_value()
(как могло быть очевидно из того, что я упомянул его вместе сoperator bool
).Переформулирую: речь шла о том, что в случае Раста разработчик не может вне unsafe контекста достать опциональное значение просто сказав, что он обещает. В коде на плюсах проблема в том, что проверка на наличие значение и его доставание могут быть как угодно размазаны по коду и в дальнейшем проверка может быть просто забыта (да и даже при написании разработчик мог её забыть). Инструментарий плюсов никак этому не помешает, кроме, быть может, линтеров, но реальной гарантии корректности у вас нет. В Расте же у
Option
операция проверки и доставания объединена, и вы просто никак синтаксически не можете выразить то, что вы достали без гарантии. Представленная вышеif let
- это один, из множества вариантов это сделать, но каждый из этих вариантов защищён от небезопасного разыменования тем, что его просто нельзя осуществить. Да, если у вас реально есть какая-то причина полагать, что разыменования возможно без проверки, то вы можете положиться сделать его без проверки, но тогда будьте добры взять ответственность на себя, сделав это через ужеunsafe
функцию (насколько я помню, таковая реализована даже как отдельный крейт), потому что тут вы берёте на себя ответственность за то, что значение есть. Причём, скорее всего, если у вас есть причины полагать, что в опциональном значении значение есть всегда, то может и вовсе не стоит тут использоватьOption
, а уместнее что-то другое? Ну а если это действительно экзотический случай и тут нужно так сделать, то тут уж пишитеunsafe
, говоря, что, да, ответственность на вас.Иначе с чего вдруг компилятору (да и просто тому, кто работает с вашим кодом) быть уверенным, что разыменования tagged unionа без проверки типа корректно?
dllmain
Формулировка более правдивая, но напомню исходный тезис из статьи: "надёжное и эффективное программное обеспечение". Управляемые языки с оверхэдом такого создавать не позволяют. То, что Rust эффективнее чем что-либо неэффективное не делает его эффективным в глобальном контексте.
Это вывод на основе частного случая(одного из самых простых), и он неверен в общем. Как я уже говорил, здесь просто не может быть оверхэда.
Этим вы сводите на нет всю концепцию безопасности Rust. Взять тот же C++ - зачем кому-то нужно, допустим, разыменовывать nullptr, неправильно алиасить типы или обращаться к уже уничтоженному объекту? Или, в более общей форме, зачем кому-то совершать ошибку в коде? Ответы на данный вопрос как правило имеют форму "сделали непреднамеренно/по невнимательности, а язык это позволяет". Так же и здесь - та же невнимательность/"завтрашний день" и т. п. И язык здесь точно так же позволяет сделать ошибку. Даже unsafe лишний раз писать не всегда нужно будет - оно уже есть в коде, и в довольно большом количестве.
Я мысль понял. В точности та же ситуация с C++. Условно говоря, .value() - это safe, operator* - unsafe. Никаких уникальных свойств Rust здесь нет.
Именно так. На других ЯП пишется "небезопасно" не потому, что там нет "безопасных" средств, а по разным другим причинам.
dllmain
Можем, как нет. Даже если бы .value() не было, обёртка пишется в одну строку.
mayorovp
Какую именно ошибку позволяет сделать язык? Вы по ссылке-то ходили?
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=eef665ddb71c401107b15eaa41bb1862
amishaa
Не просто безопасные, а безопасные и дешевые. Раст даёт довольно высокий уровень гарантий (в рамках safe rust), при этом достаточно большую скорость. Например: https://benchmarksgame-team.pages.debian.net/benchmarksgame/performance/spectralnorm.html - игрушечная задача, в который лучший код на раст так же хорош, как и лучший на c++ . При этом не содержит ни одной unsafe строчки.
А почему Вы копируете с начала объектов, а не с конца? Результат на пересекающихся массивах будет другим. И, даже если копировать с начала объектов, то часть данных можно потерять (попробуйте сделать swap от [1,2,3]; [2,3,4], где 2 и 3 - это общая память объектов. Более того, для так размещенных объектов невозможно получить ожидаемый результат ([2,3,4] по первому указателю и [1,2,3] по второму).
Программа на safe rust во многих случая работает on par с аналогичной программой на C/C++. Да, если вам нужно сделать руками векторизацию вычислений, компилятор (что плюсовый, что растовый) не всегда может это сделать.
Нет, не нужны никакие копирования, зачем? https://play.rust-lang.org/?version=stable&mode=release&edition=2018&gist=4d25250fa108fede75a349b678b24499 - никаких дополнительных копирований, содержимое Option проверили один раз, модифицировали дважды. Что именно с вектором не так? Да, проверки на выход за границу - это дополнительный оверхед. Но в практических случаях, компилятор довольно часто их может выкинуть и выкидывает.
В safe rust невозможно создать две мутабельные ссылки на один и тот же объект (и даже одну мутабельную, и ещё одну). Если вы в рамках unsafe Rust сделали две мутабельные ссылки на одну и ту же память и вернули их в safe - комилятор дальше уже ничего не гарантирует, к сожалению. Абсолютно так же можно написать unsafe функцию, которая работает с Option<NotNull> без проверки на Some. Эту функцию, после этого, нужно просто называть тоже unsafe (и иметь дополнительный инвариант, который проверять программистом, а не компилятором).
При переписывании функции с входного типа &T на Optional<T>, например, можно случайно оставить звёздочку. А в расте нельзя - не скомпилируется.
dllmain
Это так же не идея Rust. И даже не единственная реализация. И даже не первая - всё это уже есть в том же C++.
С benchmarksgame интересная история. Там кто-то засылал код на C++, который оказывался быстрее всего остального, но его удалили, поменяв требования. Насчёт unsafe - оно там есть, конечно же - в библиотечном коде. Без unsafe на Rust писать всё же нельзя.
Не дочитал вопрос. Я привёл примерный аналог тому swap на Rust. restrict там не нужен не потому, что это будет работать и с перекрывающимися областями памяти, а потому как компилятор сам всё поймёт. Очевидно, если области перекрываются нужно проверять.
Я не для этого писал про unsafe в swap. Я говорил о невозможности выразить на safe Rust элементарную операцию.
За счёт unsafe - да, сравнимый уровень получается. Но о безопасности/надёжности ПО на Rust в таком случае речи идти не может.
Да, я неверно сказал.
Да, небесплатная проверка границ. Выкинет оно только в простых случаях(примерно в тех же самых, в которых в C++ получим предупреждение о выходе за границу). Хотим убрать тормоза - пишем unsafe.
Вот эта ситуация, да. Снова всё нужно руками проверять. Как и с optional без предварительного if.
Всё равно не пройдёт. * от T(или T& - неважно) даст другой тип(условно, U&, U != T), * на optional<T> даст T&.
amishaa
Вопрос уровня гарантий. Для соблюдения гарантий safe Rust, весь unsafe код должен быть написан аккуратно. Например, safe Rust заприщает алиасинг (т.е. после завершения unsafe кода не должно получаться две мутабельные ссылки на одну область памяти). Раст - это один из быстрых языков с неожиданными сильными гарантиями. Кроме проверки типов, которую уже обсуждали, есть, например, ещё лайфтаймы.
В safe rust эта операция элементарно выражается: std:mem:swap(...). Собственно, пример безопасной абстракции, написанной на опасном rust. Она один раз написана, теперь ей можно пользоваться.
Не соглашусь. То, что безопасные абстракции там на низком уровне написаны на unsafe - не удивительно. Для питона или явы низкоуровневая реализация тоже есть, что не отменяет гарантии языка по памяти.
Довольно часто достаточно пользоваться safe-абстракциями. Например, от stdlib. И даже в stdlib подавляющая часть кода - safe.
Вот так делать нехорошо - возникает проблема с границей unsafe: написав unsafe вы гарантируете инвариант. Возможно, это накладывает нетривиальные ограничения на входные параметры функции, внутри которой Вы это сделали. В таком случае эту функцию тоже нужно помечать unsafe.
Писать unsafe код на расте сложнее, чем просто код на C++. К сожалению, формального описания "какие же инварианты на самом деле нужно поддерживать, выходя из safe", нет.
В любом случае, это какой-то повод более чётко понять как выглядят ограничения на входные данные. Например, поменять (Option<A>, Option<B>) на Option<(A,B)>, если известно, что они оба одновременно представлены или даже Option<(A, Option<B>)>, если второй не бывает представлен без первого (для массивов алгебраичиских типов уже не хватает, да).
dllmain
Как было показано выше, Rust не даёт более сильных гарантий относительно других ЯП. C++ предоставляет те же гарантии - optional::value, vector::at, unique_ptr(raw pointer в safe rust использоваться не могут), нетривиальные лайфтаймы в обоих случаях руками пишутся. Более явное разделение - да, какой-то иной уровень - нет.
Это проблема, я уже говорил о ней. Нужны потоки - опять пишем unsafe(или берём библиотеку где оно уже написано). Та же история с двусвязным списком, деревьями с дополнительными связями(ссылка на родителя - простейший пример), мультииндексами и прочими подобными данными.
В safe Rust выражается не swap, а вызов unsafe кода. Опять же, оно везде так. Точнее, на Rust выражается даже swap, но в варианте с временной копией объекта - большие объекты так свопнуть не получится(места на стеке не хватит).
Ну да, высокоуровневая реализация в части случаев невозможна в принципе, а в другой части неэффективна. О чём и речь.
Об этом я также писал выше - такое здесь просто не обсуждаем. Вызывать готовые функции может что угодно, причём вполне безопасно.
Так делать не хорошо и не плохо - это просто выражение требований к ПО. И совмещать здесь не получится - либо безопасность, либо производительность.
Думаю, их нигде и не будет, ведь ответ на этот вопрос зависит только от конкретного кода, от предположений на которые он опирается.
amishaa
Unsafe - почти не даёт дополнительных гарантий безопасности. А вот safe rust - даёт.
У любого языка программирования там где-то глубоко внизу unsafe.
Чужие безопасные абстракции, написанные на unsafe rust - это то самое свойство Раста (быстрые безопасные абстракции). В предположении, что unsafe часть написана корректно, остальной код уже не может привести к широкому классу ошибок. Safe rust поверх стандартной библиотеки, кажется, концептуально не отличается от питона: где-то внизу есть "опасный" код, но любая программа, которая компилируется не может содержать, например, двойного освобождения памяти.
Безопасный двусвязанный список уже есть реализованный. Да, концепция владения раста довольно плохо ложится на структуры с циклическими ссылками. В safe rust это тоже можно реализовать (с помощью Rc или Weak), но будут проблемы с перформансом. А в unsafe rust - можно, но тоже довольно сложно, чтобы интерфейс был безопасным.
Подождите, если я делаю std:mem:swap, то выражается ровно тот swap, который нужен. И выражается он в safe rust (потому что эта функция стандартной библиотеки помечена безопасной).
Всё программирование - это вызов готовых функций в правильном порядке. Ваша реализация swap на c++ - тоже пользуется присвоениями, например. Вопрос исключительно в уровне абстракции и в том, сколько она стоит.
Кажется, мы в этом месте ходим кругами. Идея (и судя по benchmarksgame довольно успешная) заключается в том, что на безопасном расте можно получить довольно производительный и быстрый код. Да, для этого нужен фундамент (написанный на unsafe rust, да он есть не везде), там где он есть - поверх него можно написать быстрое и безопасное приложение.
При этом есть возможность этот фундамент расширить. Для кода, расширяющего фундамент (unsafe), требований больше, чем в C++. Зато результат получается более сильным - если получилось сделать безопасную абстракцию (и реализовать её без ошибок), то можно писать безопасный код, у которого много гарантий проверяется компилятором.
dllmain
Повторяемся. Я уже показал эквиваленты в C++, предоставляющие те же гарантии.
Вы опять повторяетесь. Это везде так. И ещё, по поводу double-free: http://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=rust
An issue was discovered in the algorithmica crate through 2021-03-07 for Rust. There is a double free in merge_sort::merge().
In the standard library in Rust before 1.52.0, a double free can occur in the Vec::from_iter function if freeing the element panics.
An issue was discovered in the id-map crate through 2021-02-26 for Rust. A double free can occur in remove_set upon a panic in a Drop impl.
И так далее. Поэтому не только может, но и содержит. В том числе в std.
Нет. Выразить - значит реализовать в данном случае. Использовать проблем никаких, это понятно. Но реализуется на safe Rust только неэффективный аналог, поэтому снова unsafe.
Обобщение не верное, конечно, но в контексте swap мысль ясна. Присвоение байта - это примитив железа, а не библиотеки или языка. Очевидно, без железа код работать не сможет. Т. е. я просто использовал машкод в другой нотации. В противовес этому - в Rust swap является программным примитивом.
Не кажется - точно. Об этом я так же говорил. Но да ладно - переписывать третий раз лень.
amishaa
Гарантии safe rust выше, чем гарантии C++ (даже с линтером). Например вот здесь обсуждалось, что даже мощные линтеры к С++ не отвергают некорректный код, который отвергает раст (да, Раст иногда отвергает корректный код, корректность которого он не может доказать. По теореме Райса, если ты отвергаешь весь некорректный код, то обязан отвергать и какой-то корректный).
Гарантии unsafe rust действительно близки к гарантиям C++ (чуть получше с арифметикой, зато программисту нужно ещё поддерживать инварианты алиасинга на границе unsafe).
amishaa
Разрешите, начну с конца.
Вот это очевидно не правда. Верно, что для любого кода на Rust можно написать код на LLVM, который делает то же самое. Линтеры к C++ вообще пользуются компилятором. Следует ли из этого, что они в принципе не могут делать того, чего не делает компилятор? (И, например, анализом потока исполнения в линтере не находят больше ошибок, чем gcc/clang?)
Да, в безопасном растре есть модель владения памятью, которую он навязывает (например, никаких других ссылок на объект, если на него есть мутабельная ссылка). Да, она не общая (в том смысле, что есть корректные программы, которые используют другую моделью владения памятью). Если Вы в рамках unsafe, например, получили вторую мутабельную ссылку на объект, то да, safe rust никаких гарантий уже не даёт.
C++ довольно часто не отвергает некорректный код, а спокойно его компилирует в UB.
Раст отвергает код, в котором нарушены инварианты. И шаринг не запрещён - на один объект может быть сколько угодно ссылок. Запрещает мутабельный шаринг - если есть хотя бы одна мутабельная ссылка, то больше ссылок быть не должно. Это позволяет, например, бороться с гонками данных.
Подождите. Либо перетекают в safe с оверхедом (как linked list построенный на
Option<Rc<RefCell<T>>>)
, либо перетекают в safe абстракцию, написанную на unsafe rust (как swap или std:collections:linkedlist), либо модель памяти safe rust не совместима с вашим алгоритмом (и тогда использовать для реализации вашего алгоритма раст - это плохая идея).Option<&T>, например, реализован без оверхеда - известно, что ссылка не бывает нулевой, в результате вся структура по размеру занимает ровно ссылку (со специальным значением 0 для None). Как мы уже обсуждали выше, swap так же работает совсем без оверхеда. Вообще довольно большАя часть абстракций, представленных стандартной библиотекой, содержит небольшой оверхед, если содержат.
amishaa
В Unsafe rust можно сделать double free. Вроде, никто этого не отрицал.
Да, именно так. Любые програмные примитивы - это машкод в другой нотации (или, наоборот, любые програмные примитивы, включая C++ - это не машкод, компиляция с оптимизацией может много всего переставить или даже векторизовать). В safe rust swap - это програмный примитив с примерно нулевой дополнительной стоимостью (при компиляции). Что и записывалось в преимущества (и откуда началась эта ветка):
amishaa
У safe rust есть гарантии от компилятора. У unsafe rust - гарантий от компилятора гораздо меньше (и так же как в C# unsafe может сломать выполнение где-то дальше, а не в моменте вызова unsafe).
В тот момент, когда нужна арифметика указателей (а точнее разыменование указателя, полученного арифметическим действием) - нужен unsafe. Примерно потому что компилятор в compile time не может проверить (в общем случае), что в коде должно быть именно написанное вами
, а не pl <= epl (вместо !=) во второй строке. Формально проверять границы в compile time на голых указателях невозможно (и в итоге появляютсе оверхеды, если это делать каждый раз).
Там последовательность нескольких функций с дерективой inline. Компилятор отдельно очень попросили всё свернуть вместе.
Во-первых, в какой момент быстродействие стало частью семантики языка? gcc разных версий (или gcc и clang) компилирует один и тот же код (написанный на C++11, например) в разные программы с разной производительностью. Значит ли это, что между релизами gcc у языка поменялась семантика?
Во-вторых, оптимизации llvm, полезные для раста, разработчики раста тоже делают (и их вклад в llvm ненулевой).
DarkEld3r
Покажу
panic::catch_unwind
, но боюсь начнутся разговоры, что это "костыли", что мягко говоря, передёргивание. Так как "не рекомендуется" использовать по двум причинам: первая — это то, что в языке подталкивают делать обработку ошибок наResult
вместо паники и второе — на уровне бинаря можно выбрать стратегию обработки паники. Но если мы пишем не библиотеку, то это не проблема.Было бы очень странно будь оно иначе: сначала включаем
panic = abort
, чтобы выкинуть код размотки стека, а потом удивляемся.Ясно. Что ж про "ворованную скриптуху" ничего не было? Тогда можно было бы время не тратить.
Hesser
Я уверен, что я скажу некую глупость, но мне как самому обычному разработчику есть вопрос, а почему я должен ловить исключения через try/catch а не обрабатывать сразу ошибки через Result как это вроде сделано в rust?
Я не знаю C++, работаю только с C# и мне непонятна такая агрессия по отношению к rust, хотя вроде даже большие игроки им заинтересовались...
Hesser
Но извольте, разве внесения кода на языке Rust (ну или на интерпретаторе llvm, как вам будет удобнее) в ядро linux, как говориться "наравне" с C не является интересом к нему? Причём судя по всему продвигал это Google ссылаясь на то, что это сократит число ошибок памяти.
Мы все знаем про "кладбище идей" Google, но получается Rust теперь с нами надолго.
BlessMaster
У Result нет проблем. Это идиоматичный и стандартный способ писать обработку ошибок в Rust, имеющий поддержку в том числе со стороны компилятора (напоминающего, что Result - не обработан).
Но иногда обработка ошибок не нужна. Бывает достаточно того, чтобы программа просто завершалась в случае ошибки с указанием места, где ошибка произошла, выдав стандартный стектрейс. Удобно во время разработки, пока ещё далеко до окончательной production-ready версии кода.
Или же, бывает так, что разработчик гарантирует инварианты, инкапсулируя их где-то в приватных методах, так же, как это делается с unsafe, в то время как в интерфейсе где-то под капотом присутствует обязательный Option/Result. Например, какое-то поле всегда Some до вызова Drop::drop, а после вызова drop - к нему никто уже не обратится.
unwrap() решает эти вопросы.
Также он легко ищется по тексту и замечается во время подготовки коммита и при кодревью.
В отличие от некорректной работы с указателями, с которыми сравнивается тут где-то выше в обсуждении - Result/Option с unwrap() не создаёт UB и помогает отслживать ошибки.
> У меня нет агрессии - агрессия есть у его адептов, которые именно что агрессивно его продвигают.
Вы пришли в комментарии под обычной новостью о проходном релизе продукта, который не находится в сфере ваших интересов, и устроили срач на тему "не нужен", "ничего нового", "никакой ценности". Серьёзно, "никакой агрессии"? Вас какие-то злобные "адепты" плевками и пинками сюда загнали вот это вот всё писать? =)
amishaa
Вообще то throw (в виде panic!) он тоже вполне написать может. Если нужна паника - можно никакого unwrap не делать.
Зачем для raii (в растовском варианте) нужны обрабатываемые исключения? Зачем нужна какая-то обработка паники (типа выполнения деструкторов на raii) - понятно. Но зачем нужна абстракция ловли паники?
amishaa
При наличии обработчика выше передавать наверх result, а не паниковать, вроде ничем не плохо.
? позволяет привести тип ошибки к тому типу, который нужен функции по сигнатуре. Минимальный пример.
В не-ядерном коде можно сделать очень немного, если попытка push закончилась ошибкой нехватки памяти. Я бы ожидал, что в тех местах, где разумные действия есть сейчас (или появятся в будущем) - интерфейсы поменяются, будут как раз result.
У них есть другие проблемы. Например, как написать unsafe rust так, чтобы не было проблем с алиасингом.
technic93
Очень много функций аллоцируют, ходят в массив по индексу, и потенциально вызывают эти и другие паники. Так вот их протаскивать через Result очень неприятно, потому что в сигнатуре каждой функции добавится Result, в каждом enum с ошибкой добавится одно поле, и в каждом match соответствующий кейс для ошибок перечисленных выше.
Поэтому сейчас вся инфраструктура раст пользуется для таких ошибок неявной обработкой через механизм исключений (паника + catch). А result используют для ошибок связанных с высокоуровневой бизнес логикой.
А если мы захотим делать try_push и т д, чтобы ловить ошибки аллокации, то уже существующие библиотеки могут превратится в тыкву, потому что там внутри использовался просто push.
amishaa
Я бы в этом месте ожидал разделение библиотек. Если есть высокоуровневая бизнес-логика, то, вроде бы, catch почти никогда не нужен (с практической точки зрения) - если у тебя нет памяти и try_push вернулся с ошибкой из-за нехватки памяти, то у тебя очень ограниченное пространство вариантов (по большому счёту, ровно упасть). А вот если ты что-то делаешь на более низком уровне - Result оказывается хорошим вариантом, я бы ожидал, что там станет больше функций вида try_something.
AnthonyMikh
Объект. RAII-шный. И где тут паника при создании? Тело
Dropping::new
вообщесостоит из единственной конструкции ret
.AnthonyMikh
То есть таки не любое создание RAII-шного объекта — паника.
amishaa
Нету там dyn. Вот вариант без дополнительной аллокации (конкретно в этом случае есть строка от io::Error): https://play.rust-lang.org/?version=stable&mode=release&edition=2018&gist=149154ed96e5e151ab48c3e52c200640
Преобразование типа явное - см. описание оператора ? "
?
is used at the end of an expression returning aResult
, and is equivalent to a match expression, where theErr(err)
branch expands to an earlyErr(From::from(err))
, and theOk(ok)
branch expands to anok
expression"А потом ловите не то исключение, которое ожидали.
Обрабатывать null в malloc имеет смысл, чтобы сразу упасть и не перепелить случайно память от момента получения этого null до падения (и не закрывать ресурсы с распиленной памятью). Или вы обсуждали что-то другое?
RO шаринг в расте есть и хорошо работает. RW тоже есть, но там появляется оверхед в рантайме.
amishaa
Пожалуйста: https://play.rust-lang.org/?version=stable&mode=release&edition=2018&gist=5d2ff84acad55af5c852bda8b5761018
Да, там arc, но это safe-костыль (альтернативно можно сделать этот mutex глобальным).
amishaa
Посмотрите на версию без дополнительной аллокации: там kind является объединением ioErrorKind и IntErrorKind . И динамически ничего не резолвится (и ошибка AppError{"asd", "asd"} уже не возникает).
DarkEld3r
Почему RAII не работает без исключений? Да даже в С RAII было бы полезно (и существует в виде cleanup атрибута).
DarkEld3r
"Мусорный костыль" потому что ты так написал? Это не так работает.
"Многословно" и "неуюзабельно" — это разные вещи. Возможность перехватить и обработать панику есть. Да, не так удобно как
catch
, но это как раз потому что в языке подталкивают не использовать панику для обработки ошибок. К слову, пишу на расте больше четырёх лет иcarch_unwind
приходилось использовать буквально пару раз, причём большая часть из них — в байндингах. А уж матчиться по типу паники так и вовсе пришлось аж один раз — в тесте.Можно хотя бы несколько примеров? Не припоминаю такого в основных учебных материалах.
По ссылке написано ровно о том, о чём я только что сказал. И предупреждение относится к авторам библиотек, которые действительно не могут полагаться на конкретную стратегию обработки паники. При разработке приложений таких ограничений нет. Впрочем, мне попадались и библиотеки, которые требовали
panic = unwind
.DarkEld3r
Можно раскрыть мысль? Почему
unwrap
не позволяет "ловить ошибки"? Что значит "нет нормальных средств" для перехвата паники?PROgrammer_JARvis
Где именно?
В вызове unsafe-функции? - потому что это также unsafe действие.
В определении unsafe-функции? Потому что она ведёт к UB, если вызывающий не соблюл инвариант (неалиасность указателей).
Потому что существует реализация свапа для указателей, которая умеет делать это очень быстро при условии соблюдения инварианта. Этот инвариант гарантирован для
&mut T
, а, значит, нет необходимости делать повторную реализацию для него, когда можно воспользоваться той.Почему же вы считаете, что это нельзя выразить формально на Расте?
От чего же нельзя? Можно:
Но это та самая наивная и неэффективная реализация, от которой мы старались уйти. Причём, ограничение в виде реализации
Clone
присутствует и в C++ версии, а именно требование на copy- и move-assignable (надеюсь, cppreference будет достаточно корректным источником тут).Библиотечный примитив мог быть реализован и так, но (и это уже детали реализации) была возможность сделать это, используя unsafe.
Плохо ли это? По-моему, нет, потому что это никак не влияет на пользователя безопасной функции
swap
. Он наоборот получает в составе стандартной библиотеки необходимый примитив и не должен сам заботить себя необходимостью где-то вручную проверять его корректность или, вообще, предпринимать какие-либо действия для его работоспособности.Что же касается тезиса о том, что стандартная библиотека написана с большим количеством unsafe, то в чём именно проблема этого? В Расте есть unsafe и никто этого не отрицает, но идея в том, что в клиентском коде его прекрасно получается избегать.
Иначе (далее утрированный пример) можно точно так же сказать, что почему-то выразить средствами C++ (практически любого ЯП, на самом деле) сложение двух целых чисел можно, но это будет очень медленно (цикл (который, как и случай со swap, может сам требовать некоторый более примитивный аналог самого себя, в данном случае для счётчика цикла)) либо небезопасно (машинно-специфичный АСМ с учётом размерности, эндианности etc, причём это ещё если данный ЯП такой инструмент даёт). А то, что для этого есть "особый оператор +" считать признаком слабости языка.
Или же говорить, что использование инлайн-АСМа это признак слабости языка.
Очень даже используются, использование двух mut-ссылок гарантирует, что они не алиасятся, что и является соблюдением инварианта unsafe-вызова для полученных из них указателей, которое (соблюдение неалиасности ссылок) в свою очередь гарантирует нам компилятор.
Почему же нет никакого смысла? Это хинт компилятору (не важно, какой там бэкенд, LLVM, GCC, whatever), что данная ветка гарантированно (разработчиком) недостижима, и компилятор (снова же, я не ссылаюсь на какой-то конкретный, а говорю, в общем, что значит эта (псевдо)функция) имеет право рассматривать это, как истину в последней инстанции. Несоблюдение этого инварианта есть UB, и не потому, что "так где-то сделано", а потому что вся суть этой функции в том, чтобы дать строгую статически-недоказуемую гарантию. Отсутствие unsafe (то есть, UB при несоблюдении инварианта) значило бы, что даже если ветка была бы достигнута, она должна была бы как-то конкретно быть обработана, для чего и существует не-unsafe аналог, который паникует (можно провести аналогию с плюсовым исключением) при достижении.
Конкретно в данном примере корректность условной деконструкции.
Конкретно конструкция if-let является общим инструментом сравнения с паттерном, а не каким-то частным случаем для
Option
(не говорю, что Вы подобное утверждали, но, на всякий случай, здесь уточню это, чтобы не было дальнейшей полемики на эту тему).Причём, опять же, что плохого в том, что семантика примитивная? Она простая, и при этом позволяет чётко описать действие - это же (синтаксис) и входит в понятие инструментария языка, о котором шла речь изначально.
А что до бесплатности, то куда ему быть ещё бесплатнее? Это пара проверка+доставание, которая в том же C++ (да и, в целом, любом ЯП) одинаково дорогая. Причём тут, поскольку это часть синтаксиса языка, очевидно, что это одно из первых мест, которые оптимизируются во всю (относительно, условно, каких-то конкретных функций).
А что именно тут не так? Это и не type refinement, формально, а просто pattern matching. Хотя играет он тут ту же роль: ограничивает в пределах блока
if
множество значенийvalue
типомT
(сахара ради, новая переменная внутри if-let может, иметь точно такое же название, как и изначальная переменная, а ляif let Some(value) = value
).В том то и дело, что в C++ в абсолютно изначально корректном месте приходится использовать unsafe (иначе говоря, UB-провоцирующую) функцию тогда, когда она таковой не является. Вся причина в том, что нету той самой пары проверь+возьми одним "атомарным" выражением, и система типов действительно делает тот UB синтаксиески возможным.
Вы совершенно верно подметили ниже функциональный вариант с
map
, который не пользуется спросом. Функциональный вариант тут и менее читаем (в некоторых случаях, хотя бы из-за громоздкости синтаксиса лямбд) и менее удобен (банальный capturing ставит палки в колёса) да и, полагаю, не очень идиоматичен.Вы выше, если не ошибаюсь, писали, что Раст не даёт ничего для безопасности с нулевой ценой, но ведь тут именно оно - операция, которая в C++ описывалась парой никак не связанных statement'ов, тут представлена одним, при этом не оставляя места даже для формального UB.
Суть паник именно в том и состоит, чтобы сообщить о том, что какой-то хрупкий (=недоказуемый статически) инвариант был нарушен, не провоцируя при этом UB, но использование их для какой-либо бизнес-логики это явный антипаттерн. Ровно как в C++ не стали бы (я полагаю) ловить какой-нибудь
std::invalid_argument
.Тот же самый
unwrap
, как раз, не столь "идиоматичный" (в виду наличия всё того же if-let, match и кучи функциональных методов), но позволяет, с одной стороны, гарантированно со стороны разработчика, но с другой - без UB (а с паникой в случае его ошибки) описывать случай, когда он знает, что значение есть, хоть и представлено вOption
(например, когда это некоторый трейт, который возвращает, в общем случае, опциональное значение, но для какого-то конкретного типа оно всегда присутствует, причём с окончательным прибытие never-type (!
) многие места, где нынеunwrap
, будут прекрасно безусловно (let вместо if-let) раскрываться для, к примеру,Result
).---
Что же касается того уточнения, что при помощи
unsafe
можно получить два алиасищихся&mut T
, то это и есть то самое UB, которое должен избегать соблюдением инварианта разработчик, и если вunsafe
-блоке некто сделал бяку и вернул в результате два некорректных mut-указателя (в данном случае, указывающих на пересекающуюся зону) то это и есть UB, и гарантий, когда именно оно выстрелит, нельзя дать. Иначе говоря, наша программа уже, формально, в некорректном состоянии, а не вызовswap
является небезопасным.