Pull to refresh

Comments 112

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

Всё же нет, к сожалению. Во первых, в коде на Rust слишком много unsafe, начиная со стандартной библиотеки. Безопасность здесь так же лежит на плечах программиста.

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

Все же нет. Причина наличия unsafe - это наличие другого семантического ядра, в котором компилятор не может самостоятельно проверить условия. Почитайте диссертацию Ральфа Юнга - https://people.mpi-sws.org/~jung/thesis.html.

Кстати, в ней же он доказывает возможность создания безопасного кода на костях unsafe.

Причина наличия unsafe - это наличие другого семантического ядра, в котором компилятор не может самостоятельно проверить условия.

Вы хотели сказать наличие конструкций, которые невозможно выразить на safe Rust(иначе другая семантика просто не нужна). Тот же двусвязный список, например. Я не учитывал эти случаи несмотря на их многочисленность, потому как посчитал это очевидным.

Кстати, в ней же он доказывает возможность создания безопасного кода на костях unsafe.

Ну это не имеет значения. Возможность есть для любого языка.

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

Rust – это на самом деле два языка программирования (Safe Rust и Unsafe Rust), причем Safe Rust является подмножеством Unsafe Rust. Эта особенность позволяет разработчикам создавать надежные И эффективные абстракции для небезопасных по свое природе операций, а затем безопасно И эффективно использовать их в прикладном коде.

Так что да, Rust позволяет создавать надежное И эффективное ПО.

Если всё так как вы говорите, то Rust отличается от C или C++ только тем, что он новый и с самого начала приветствует использование только "библиотек", не поощряя использование основного языка. Ну и "библиотеки" направлены в первую очередь на безопасность, во вторую на производительность.

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

Если Rust ничем не отличается от C/C++, то почему в нем реализованы отдельно ссылки и указатели? Не стоит так безапеляционно заявлять о том, в чем вы не разобрались. Очевидно, в C/C++ не заложен тот инструментарий контроля за безопасностью, который реализован в Rust на уровне языка.

Безопасный код на Rust может упасть с segmentation fault если в нижележащем низкоуровневом коде была допущена ошибка, как и абсолютно любой другой "безопасный" код написанный в другой парадигме.

Rust предлагает новую парадигму построения абстракций между Safe и Unsafe, потому что в основе любой безопасной реализации будут лежать небезопасные примитивы, корректность использования которых лежит на разработчике. Основная идея Rust – локализовать применение небезопасных примитивов, обернув их в безопасную абстракцию. И это работает.

Ваш второй комментарий противоречит первому. "Вы либо крестик снимите, либо трусы наденьте." Мой комментарий основан исключительно на вашем первом комментарии. Я на Rust написал только "hello world".

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

Unsafe Rust действительно по уровню гарантий и поведению близок к C/C++, но в нем, к счастью, очень короткий список undefined behaviour.

Safe Rust по уровню гарантий похож скорее на Java, но не является managed языком: ни виртуальной машины, ни сборщика мусора там нет.

Писать на Rust – это примерно как писать на C низкоуровневые библиотеки, и затем использовать их на Java в основном коде, с той лишь разницей, что синтаксически Safe/Unsafe Rust – почти идентичные языки, которые бесшовно интегрируются, без каких-либо накладных расходов.

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

В действительности, 90% Rust-разработчиков никогда не пишут на Unsafe Rust, но весь их код так или иначе основан на Unsafe Rust. Стандартная библиотека языка – это по сути сплошной unsafe-код, завернутый в абстракции, корректное использование которых гарантируется компилятором.

Unsafe Rust действительно по уровню гарантий и поведению близок к C/C++, но в нем, к счастью, очень короткий список undefined behaviour.

Список там как в Си и плюс ещё вагон связанный с алиаснигом.

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

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

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

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

UFO just landed and posted this here
UFO just landed and posted this here

Царь, ты зае надоел срать на Хабре. С нетерпением жду, когда из тебя с неизбежностью вновь попрёт шизофазия и тебе справедливо сольют карму.

UFO just landed and posted this here

Ну Вы на святую святых посягнули, в подобных ситуациях можно и еще более весомые аргументы услышать :)

Таким аргументам не место на Хабре. У нас технический ресурс, надо отвечать по сути вопроса или молчать.

Абсолютно с Вами согласен.

Я слишком хорошо знаю, что это за персонаж. Это далеко не первый его аккаунт тут. Агрессивен, малообразован, превозносит C++, поносит Rust, уровень технической аргументации ниже плинтуса, постоянно пытается продвинуть идею о том, что популярность Rust обусловлена не техническими причинами, а тех, кто с ним не согласен, записывает в мифических "адептов".


Вот увидите, скоро опять начнутся и "запартные поломои", и "школьники", и "макаки", и истерика про ворованный компилятор.

UFO just landed and posted this here

Агрессивен? Скажем так - не более, чем иные раст-адепты здесь. Одному крабообразному из вашей тусовки здесь недавно тоже крышу сорвало на почве неудобных вопросов, пришлось в readonly отправлять. Малообразован? Не сказал бы. Малообразованными выглядят скорее некоторые адепты раста, у которых в активе ничего, кроме лозунгов. Превозносит С++, поносит раст? Вы занимаетесь тем же самым, только у вас языки местами переставлены - но "это другое". Никакой популярности у раста нет, есть хайп, а мнение этого гражданина о пользе хайпа для бизнеса (из коей, по его мнению, растут ноги расто-пиара про "поддержку крупных игроков") вполне имеет право на существование. Не надо здесь угроз типа "доиграешься, придут активисты и накажут", это не тот ресурс.

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

Rust предлагает новую парадигму построения абстракций между Safe и Unsafe, потому что в основе любой безопасной реализации будут лежать небезопасные примитивы, корректность использования которых лежит на разработчике. Основная идея Rust – локализовать применение небезопасных примитивов, обернув их в безопасную абстракцию. И это работает.

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

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

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

Например, деректива #![forbid(unsafe_code)] запрещает писать unsafe в крейте.

Хорошо, правда это не совсем "гарантируется компилятором", это "компилятор может помочь отследить использование unsafe". Даже по умолчанию unsafe не запрещён. Но ладно, принимается.

Итого по данной ветке: Rust позволяет более явно разделять safe и unsafe(с возможностью запрещать последний локально). То есть просто интеграция части инструментария в компилятор. Здесь нигде нет даже предпосылок для надёжного и эффективного ПО, и уж тем более новой парадигмы.

UFO just landed and posted this here

Компилятор гарантирует, что если stdlib написан корректно, то safe код, написанный поверх stdlib, не приведёт, например, к разименованию нулевого указателя.

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

UFO just landed and posted this here

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

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

Для stdlib (и любой другой библиотеки использующей unsafe) сам компилятор корректность гарантировать не может. Так же как с C# unsafe - можно выстрелить себе в ногу в unsafe блоке, а упадёт само приложение где-то дальше.
Но никто не мешает написать быструю библиотеку для чего-то своего поверх stdlib. Как раз в силу того, что абстракции дешёвые во время исполнения (и компиляции) она будет достаточно близка к low-level.
Да, если нужна работа с железом (на уровне регистров, например), придётся писать какой-то unsafe код (или пользоваться другой библиотекой, которая предоставляет безопасную абстракцию поверх этих регистром).

UFO just landed and posted this here
Нет, она никогда не будет близка к лоулевел. Просто потому, что stdlib это просто api, который позволяет хоть как-то делать примитивные вещи.

Может вы приведете хоть какие-то доказательства таким голословным утверждениям? Графики, бенчи, исследования?

UFO just landed and posted this here

Так и нет ответа почему вдруг всё свелось к stdlib.

Утверждение "Компилятор гарантирует, что если stdlib написан корректно, то safe код, написанный поверх stdlib, не приведёт, например, к разименованию нулевого указателя." абсолютно прозрачно меняется, если поменять stdlib на другой источник unsafe кода.
"Компилятор гарантирует, что если весь unsafe код (включая код зависимостей) написан корректно, то safe программа не вызовет разыменование нулевого указателя".

UFO just landed and posted this here
UFO just landed and posted this here

Это, очевидно, баг в подсчёте ограничений в where clousre. Вот иллюстрация той же самой проблемы, без использования 'static: https://github.com/rust-lang/rust/issues/84591.

И не twos complement - это не баг

Если бы не two compelemnt был бы багом, то все оптимизации компилятора, которые завязаны на не-переполнение signed int, были бы багом. Вообще, "переполнение знаковых - это undefined behaviour" - это фича языка, а не бага - оптимизировать код, в котором программист задумался и избежал можно лучше (и компиляторы это делают).
Другое дело, что на результат работы программиста это накладывает дополнительные ограничения.

UFO just landed and posted this here

Почему оно не починено тогда?

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

уб - это не фича. Уб это просто отсутствие спецификации на поведение. Оно может быть обусловлено чем угодно, в том числе и багом.

Нет. В стандарте C++ разделены unspecified и undefined behavior.
Undefined behavior - это именно о том, что компилятор в рамках оптимизации Вашей программы на C++ может предполагать, что Вы (как программист) гарантировали, что оно не случится.

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

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

Ещё раз, если проще. Без проблем можно сказать, что "поведение при знаковом переполненении просто забыли специфицировать и влепили УБ". Всё будет точно так же работать.

Ну нет же. Сегодня компилятор C++ имеет полное право заменить for(int i = 0; i > 0; i++) на while(true) с целью оптимизации (если, конечно, i не используется в теле цикла). А пользуется он этим правом или не пользуется - это его решение (и для сохранения поведения нужно привязываться к gcc/clang одной конкретной минорной версии)

UFO just landed and posted this here

Во-первых срочно стоит обновить методичку - в С++ знаковое переполнение не является УБ.

А есть ссылка на источник где об этом написано?

UFO just landed and posted this here

Мимо - это не баг питона. Это баг в какой-то либе. К языку не имеет никакого отншения.

Прочитали бы внимательно. Это баг в PyModule_AddIntConstant(), которая часть ядра питона.

Ога, любой баг не критичный. Особенно в том, о чём на каждом шагу орём "гарантирует".

Там ещё 70 таких багов. Их постепенно закрывают. Учитывая, что вот этот похожий пометили high, есть довольно хорошие шансы, что наконец пофиксят (оба сразу).

Undefined никаким образом не связан с оптимизациями и прочей чушью. Undefined это полностью неспецифицированное поведение.

Ага, и поэтому программа с UB может давать разный результат, если скомпилированна с -O0 и -O3? И ключ с -fno-strict-aliasing (с которым нужно собирать ядро линукса) выключен по-умолчанию для причин не связанных с оптимизациями компилятора?

Во-первых, выше доказано, что поведение раста может зависеть зависеть от версии компилятора

Может. Например, часть некорректного кода компилируется старым компилятором, но не компилируется новым (потому что пофиксили ошибку, которая разрешала некорректный код, с трейтом fake-static случится именно это). Часть корректного кода компилируется новым компилятором, но не компилируется новым (потому что какие-то проверки в safe делают менее жёсткими). Отдельно, код, содержащий некорректный unsafe, может тоже собираться по-разному, даже минорными версиями (как и код содержащий некорректный с++).

Во-вторых С++ не обязательно должен быть стандартным. Поэтому все эти истории "C++ может предполагать, что Вы (как программист) гарантировали" так же являются чушью, в том числе и по этой причине.

Поэтому, как С++-программист я могу ссать на стандарт. И ссал. Потому что мир С++ не ограничивается стандартом. А если ограничивается, то никакого раста не существует.

И это действие превращает Вас из C++ программиста в программиста на gcc10.2.0 , гарантий, что код написанный вами, скомпилированный 10.2.1 даст тот же результат уже никто не даёт.

Если же мы говорим об реализации и её поведения, то это поведение определяется реализацией, очевидно. И никакого УБ в ней существовать не может. Всё зависит от того, что мы используем в качестве источника истины. Поэтому я без проблем могу выкинуть стандарт, а значит УБ для меня перестали существовать.

Выкидывание стандарта привязывает вас к минорной версии (см. выше или ниже).

Во-первых срочно стоит обновить методичку - в С++ знаковое переполнение не является УБ.

Про оптимизации и прочую чушь я писал выше. Ни к каким оптимизациями УБ отношения не имеет.

Ответил тоже выше - имеет отношение к оптимизациям кода, которые делает компилятор.

С чего вдруг минорной? Чем это обусловлено?

Изменение поведения кода, содержащего UB не является ломающим изменением.

UFO just landed and posted this here

Как мне получить этот баг на уровне языка. Показывайте пример.

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

А может не давать. Показывайте, где в стандарте описана связь УБ с какой-то оптимизацией.

Вы ищете пример кода, который с O2 и с O0 даёт разные результаты на большинстве современных компиляторов?

Полнейшая чушь нелепая. Кстати, а почему адепт данной чуши не является программистом на rust10.2.0? Ведь для его кода так же ничто не гарантирует тот же результат на следующей версии?

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

Кстати, открою тайну - никто в мире С++ не пишет под стандарт - все пишут под компилятор

Видимо, мы наблюдаем разные миры. В С++ рядом со мной пишут как раз под 11 стандарт. И подготовка тестовой сборки на clang (для тестов производительности, боевая у нас на gcc) заняла пару дней (для того, чтобы поменять скрипты сборки). После этого сборка прошла все тесты.

UFO just landed and posted this here

Rust – это на самом деле два языка программирования (Safe Rust и Unsafe Rust), причем Safe Rust является подмножеством Unsafe Rust. Эта особенность позволяет разработчикам создавать надежные И эффективные абстракции для небезопасных по свое природе операций, а затем безопасно И эффективно использовать их в прикладном коде.

Да, это два языка, верно. Только вы не приняли во внимание(или приняли, но ошиблись в каких-либо других рассуждениях) что unsafe предоставляет только эффективность, а safe - только надёжность. Очевидно, один и тот же код не может быть сразу и safe, и unsafe. Поэтому никакого "И" здесь нет. Вот так должен был выглядеть отрывок вашего поста, который я процитировал:

Rust – это на самом деле два языка программирования (Safe Rust и Unsafe Rust), причем Safe Rust является подмножеством Unsafe Rust. Эта особенность позволяет разработчикам создавать эффективные абстракции для небезопасных по свое природе операций, а затем безопасно использовать их в прикладном коде.

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

Я имел ввиду что создаваемые абстракции безопасные в использовании И эффективные в реализации.

Я слабо представляю себе значение фразы "эффективность в реализации", но на мой взгляд в статье совсем не об этом говорилось:

надёжное и эффективное программное обеспечение

В своём первом комментарии я говорил именно об эффективности при исполнении. Или, проще говоря, эффективность == малый расход ресурсов в runtime, насколько это возможно.

Как раз идея Раста в том, чтобы предоставить дешёвые (в идеале, с нулевой стоимостью) абстракции, которые при этом безопасны.

Достигается это именно путём того, что на низком уровне что-то (и то, относительно узкая часть, а именно "самые-самые" примитивы + интеграции с внешними функциями) реализовано с unsafe, однако предоставляемый интерфейс (в широком смысле) - safe.

Вот простой, но, как мне кажется, достаточно характерный пример, реализация swap в стандартной библиотеке:

#[inline]
pub const fn swap<T>(x: &mut T, y: &mut T) {
    // SAFETY: the raw pointers have been created from safe mutable references satisfying all the
    // constraints on `ptr::swap_nonoverlapping_one`
    unsafe {
        ptr::swap_nonoverlapping_one(x, y);
    }
}

Небезопасный 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):

match (rand(2)) 
  0 => foo(),
  1 => bar(),
  _ => unsafe {
    unreachable_unchecked(),   } 
  }
} 

Компилятор не имеет никакой возможности явно доказать, что числа кроме 0 и 1 невозможны, потому что это логика вашей rand, но вы можете сделать это, взяв ответственность за ошибку в случае, если это не так, на себя, причём строго в пределах блока unsafe. В то же самое время, если вы "не, уверены" есть и не-unsafe аналог который вставляет код для паники в место своего использования, хоть он и не даёт компилятору просто так вырезать эту ветку. Причём, в действительности, компилятор, быть может, и в состоянии сам понять, что rand(2) не может вернуть ничего, кроме 0 и 1 и в таком случае и вариант с паникой без проблем будет вырезан.

Ну и другой пример для того, чтобы показать, что то, что в Расте safe, в целом, сделано наиболее дёшево, и аналогичный код на (например) C++ был бы ни чуть не быстрее: великий и могучий Option<T>. Самый близкий аналог на плюсах - std::optional<T>. И классическая же операция дереференса. Что в расте, что в плюсах, если вы собираетесь достать из него T, вам необходимо проверить, есть ли он там вообще. В C++ для этого используется `operator bool()` или value(), который можно банально забыть написать или при рефакторинге забыть изменить. Иначе говоря, проблема подхода там - неатомарность операций:

if (opt) foo(*opt);

Неатомарность в том значении, что компилятор не сможет никак гарантированно вам помочь, если вы забудете выполнить эту проверку. Может (i.e расширения), но не обязан, потому что с точки зрения языка код без проверки всё так же корректно. А плохо это потому что "быстрый" operator * ведёт к UB, если значение отсутствует.

Раст же даёт инструментарий (причём, довольно разнообразный), при котором чисто аналогичный код атомарен:

if let Some(value) = opt {
  foo(value)
}

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

Как раз идея Раста в том, чтобы предоставить дешёвые (в идеале, с нулевой стоимостью) абстракции, которые при этом безопасны.

Это не идея Rust, как я уже говорил. Такая модель известна и используется не менее 30 лет(но скорее всего и того больше). Зачем вы приписываете себе чужие идеи?

Достигается это именно путём того, что на низком уровне что-то (и то, относительно узкая часть, а именно "самые-самые" примитивы + интеграции с внешними функциями) реализовано с unsafe, однако предоставляемый интерфейс (в широком смысле) - safe.

Что нового вы здесь сообщили? Такое есть где угодно, даже в том же питоне и прочих(там сишный код, вызываемый из питона == unsafe в Rust). Про языки помощнее типа C++ и говорить не стоит. Снова выдаёте чужое за своё.

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

Это тривиальная операция, оверхэда здесь быть не может. Кстати, примечательно что даже такой простой код написан полностью в unsafe.

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

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

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

Про гарантии и инварианты я писал выше. Unsafe есть потому как safe Rust не может выразить множество конструкций. Многопоточная код как пример - потоки просто не смогут взаимодействовать, потому как safe Rust запрещает разделение данных.

Другой неплохой пример - std::hint::unreachable_unchecked

Который снова ничего не иллюстрирует. Что вы хотели показать этим примером?

Что в расте, что в плюсах, если вы собираетесь достать из него T, вам необходимо проверить, есть ли он там вообще. В C++ для этого используется operator bool() или value(), который можно банально забыть написать или при рефакторинге забыть изменить.

Уже поближе к теме. На этом примере можно видеть полное отсутствие различий в контексте безопасности между C++ и Rust. Во первых, проверять нужно не всегда, но safe Rust не может не проверять - эффективности уже нет. Пытаемся эту эффективность не терять - пишем unsafe и теряем безопасность. Во вторых, забыть написать .value() возможно также, как и написать unsafe {} в Rust(т. е. это просто из серии "страшилки на ночь"). В ту же кучу доступ к элементу вектора по индексу.

Это тривиальная операция, оверхэда здесь быть не может. Кстати, примечательно что даже такой простой код написан полностью в unsafe.

Как правильно безопасно реализовывать swap без дополнительной памяти и внешней гарантии на то, что объекты не пересекаются по памяти?

Что нового вы здесь сообщили? Такое есть где угодно, даже в том же питоне и прочих(там сишный код, вызываемый из питона == unsafe в Rust). Про языки помощнее типа C++ и говорить не стоит. Снова выдаёте чужое за своё.

Вопрос в скорости абстракции. Написанная на питоне программа будет (почти всегда) работать существенно медленнее, чем программа, написанная на C++. Rust-абстракции в этом смысле гораздо более быстрые.

Во первых, проверять нужно не всегда, но safe Rust не может не проверять - эффективности уже нет

Если вы проверили это уже где-то раньше - поменяйте тип с Option<T> на T, после этого проверять не нужно. Если данные приходят в функцию в виде Option<T>, то проверять нужно (даже если сегодня мы знаем, что там всегда что-то есть, то завтра кто-то может попробовать переиспользовать эту функцию по её сигнатуре и передаст туда Empty).

Во вторых, забыть написать .value() возможно также, как и написать unsafe {} в Rust(т. е. это просто из серии "страшилки на ночь").

Можно забыть написать unsafe в Rust. Полученный код не скомпилируется. А вот если забыть написать .value() в C++, то полученный код скомпилируется (но на отдельных данных будет вызывать неопределенное поведение).

Кстати, похожие проверки что есть if на сравнение с null перед использованием есть в тайпскрипте.

Поэтому if let синтаксис не обязателен для работы с optional.

Как интересно вы проигнорировали часть моего сообщения. Спрошу ещё раз: что насчёт идеи о безопасных абстракциях? Почему предыдущий комментатор написал "это идея Rust", хотя она таковой не является?

Как правильно безопасно реализовывать swap без дополнительной памяти и внешней гарантии на то, что объекты не пересекаются по памяти?

Например так:

template<typename T> void swap(T& __restrict l, T& __restrict r) {
	for (auto pl{(char*)&l}, pr{(char*)&r}, epl{pl + sizeof(T)}; pl != epl; ++pl, ++pr) {
		auto c{*pl};
		*pl = *pr;
		*pr = c;
	}
}

Хотя здесь даже restrict не нужен. Если же вы про Rust - никак. Это ещё один из многих случаев, когда safe Rust не может выразить нужную операцию. Причём здесь нет ничего небезопасного или "инварианта, который компилятор не имеет возможности гарантировать" - операция элементарная.

Вопрос в скорости абстракции. Написанная на питоне программа будет (почти всегда) работать существенно медленнее, чем программа, написанная на C++. Rust-абстракции в этом смысле гораздо более быстрые.

Зачем вы это рассказываете? Программа на safe Rust также будет работать медленнее программы на C++. Если вы хотели сказать "Rust быстрее Python во многих случаях" - поздравляю, конечно, вы победили питон(наверное). Только этого недостаточно для написания эффективного ПО.

Если вы проверили это уже где-то раньше - поменяйте тип с Option<T> на T, после этого проверять не нужно.

Это нерабочий сценарий, поскольку теряется возможность модифицировать объект в option. Либо избыточные проверки, либо два лишних копирования. И это уже в простейшем случае. С вектором всё ещё хуже.

даже если сегодня мы знаем, что там всегда что-то есть, то завтра кто-то может попробовать переиспользовать эту функцию по её сигнатуре и передаст туда Empty

Постойте, вы сами себе противоречите. Выше был упомянут swap, в который завтра кто-то может попробовать передать ссылки на один и тот же объект через unsafe(и получить то самое UB). Но swap почему-то безопасен, а optional без проверки нет.

Можно забыть написать unsafe в Rust. Полученный код не скомпилируется. А вот если забыть написать .value() в C++, то полученный код скомпилируется (но на отдельных данных будет вызывать неопределенное поведение).

Ещё раз: нельзя забыть написать .value() - это так же не скомпилируется, поскольку типы будут разные. Если же вы забываете таким образом, что вместо .value() пишете *(это какая-то очень странная форма забывчивости) - вы точно также и в Rust вместо безопасного кода напишете unsafe.

Почему предыдущий комментатор написал "это идея Rust", хотя она таковой не является?

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

Например так:

template<typename T> void swap(T& __restrict l, T& __restrict r) {
    for (auto pl{(char*)&l}, pr{(char*)&r}, epl{pl + sizeof(T)}; pl != epl; ++pl, ++pr) {
        auto c{*pl};
        *pl = *pr;
        *pr = c;
    }
}

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

Постойте, вы сами себе противоречите. Выше был упомянут swap, в который завтра кто-то может попробовать передать ссылки на один и тот же объект через unsafe(и получить то самое UB). Но swap почему-то безопасен, а optional без проверки нет.

Если речь об std::swap, то почему вы в этом и предыдущем сообщении говорите о том, что кто-то будет вызывать его из unsafe, если это safe функция? Ещё раз, swap делегирует к unsafe методу swap_nonoverlapping_one, который требует соблюдения строго инварианта, но пользователя это не волнует. Потому что на вызове swap этот инвариант итак гарантирован. Задача реализации swap - вызвать swap_nonoverlapping_one, потому что два алиасных &mut T быть не может, а вот для *mut T такие гарантии должен (для данной задачи) гарантировать разработчик. Но не разработчик клиентского кода, а разработчик реализации swap. Я же как разработчик просто пользуюсь swap из safe и не задумываюсь (утрированно) над тем, как он реализован, потому что я знаю, что вызывать его неправильно я чисто физически не могу.

Ещё раз: нельзя забыть написать .value() - это так же не скомпилируется, поскольку типы будут разные.

Полагаю, проблема в моей опечатке выше, речь шла о has_value() (как могло быть очевидно из того, что я упомянул его вместе с operator bool).

Переформулирую: речь шла о том, что в случае Раста разработчик не может вне unsafe контекста достать опциональное значение просто сказав, что он обещает. В коде на плюсах проблема в том, что проверка на наличие значение и его доставание могут быть как угодно размазаны по коду и в дальнейшем проверка может быть просто забыта (да и даже при написании разработчик мог её забыть). Инструментарий плюсов никак этому не помешает, кроме, быть может, линтеров, но реальной гарантии корректности у вас нет. В Расте же у Option операция проверки и доставания объединена, и вы просто никак синтаксически не можете выразить то, что вы достали без гарантии. Представленная выше if let - это один, из множества вариантов это сделать, но каждый из этих вариантов защищён от небезопасного разыменования тем, что его просто нельзя осуществить. Да, если у вас реально есть какая-то причина полагать, что разыменования возможно без проверки, то вы можете положиться сделать его без проверки, но тогда будьте добры взять ответственность на себя, сделав это через уже unsafe функцию (насколько я помню, таковая реализована даже как отдельный крейт), потому что тут вы берёте на себя ответственность за то, что значение есть. Причём, скорее всего, если у вас есть причины полагать, что в опциональном значении значение есть всегда, то может и вовсе не стоит тут использовать Option, а уместнее что-то другое? Ну а если это действительно экзотический случай и тут нужно так сделать, то тут уж пишите unsafe, говоря, что, да, ответственность на вас.

Иначе с чего вдруг компилятору (да и просто тому, кто работает с вашим кодом) быть уверенным, что разыменования tagged unionа без проверки типа корректно?

это не что-то, что изобрёл Раст, это подход, который существует давно, но конкретно в нём позволяет на (практически) одном и том же языке писать что safe, что unsafe код, и при этом safe подмножество не несёт за собой оверхеда, характерного для управляемых языков.

Формулировка более правдивая, но напомню исходный тезис из статьи: "надёжное и эффективное программное обеспечение". Управляемые языки с оверхэдом такого создавать не позволяют. То, что Rust эффективнее чем что-либо неэффективное не делает его эффективным в глобальном контексте.

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

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

Если речь об std::swap, то почему вы в этом и предыдущем сообщении говорите о том, что кто-то будет вызывать его из unsafe, если это safe функция?

Этим вы сводите на нет всю концепцию безопасности Rust. Взять тот же C++ - зачем кому-то нужно, допустим, разыменовывать nullptr, неправильно алиасить типы или обращаться к уже уничтоженному объекту? Или, в более общей форме, зачем кому-то совершать ошибку в коде? Ответы на данный вопрос как правило имеют форму "сделали непреднамеренно/по невнимательности, а язык это позволяет". Так же и здесь - та же невнимательность/"завтрашний день" и т. п. И язык здесь точно так же позволяет сделать ошибку. Даже unsafe лишний раз писать не всегда нужно будет - оно уже есть в коде, и в довольно большом количестве.

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

Я мысль понял. В точности та же ситуация с C++. Условно говоря, .value() - это safe, operator* - unsafe. Никаких уникальных свойств Rust здесь нет.

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

Именно так. На других ЯП пишется "небезопасно" не потому, что там нет "безопасных" средств, а по разным другим причинам.

UFO just landed and posted this here

Можем, как нет. Даже если бы .value() не было, обёртка пишется в одну строку.

Как интересно вы проигнорировали часть моего сообщения. Спрошу ещё раз: что насчёт идеи о безопасных абстракциях? Почему предыдущий комментатор написал "это идея Rust", хотя она таковой не является?

Не просто безопасные, а безопасные и дешевые. Раст даёт довольно высокий уровень гарантий (в рамках safe rust), при этом достаточно большую скорость. Например: https://benchmarksgame-team.pages.debian.net/benchmarksgame/performance/spectralnorm.html - игрушечная задача, в который лучший код на раст так же хорош, как и лучший на c++ . При этом не содержит ни одной unsafe строчки.

Хотя здесь даже restrict не нужен. Если же вы про Rust - никак. Это ещё один из многих случаев, когда safe Rust не может выразить нужную операцию. Причём здесь нет ничего небезопасного или "инварианта, который компилятор не имеет возможности гарантировать" - операция элементарная.

А почему Вы копируете с начала объектов, а не с конца? Результат на пересекающихся массивах будет другим. И, даже если копировать с начала объектов, то часть данных можно потерять (попробуйте сделать swap от [1,2,3]; [2,3,4], где 2 и 3 - это общая память объектов. Более того, для так размещенных объектов невозможно получить ожидаемый результат ([2,3,4] по первому указателю и [1,2,3] по второму).

Зачем вы это рассказываете? Программа на safe Rust также будет работать медленнее программы на C++. Если вы хотели сказать "Rust быстрее Python во многих случаях" - поздравляю, конечно, вы победили питон(наверное). Только этого недостаточно для написания эффективного ПО.

Программа на safe rust во многих случая работает on par с аналогичной программой на C/C++. Да, если вам нужно сделать руками векторизацию вычислений, компилятор (что плюсовый, что растовый) не всегда может это сделать.

Это нерабочий сценарий, поскольку теряется возможность модифицировать объект в option. Либо избыточные проверки, либо два лишних копирования. И это уже в простейшем случае. С вектором всё ещё хуже.

Нет, не нужны никакие копирования, зачем? https://play.rust-lang.org/?version=stable&mode=release&edition=2018&gist=4d25250fa108fede75a349b678b24499 - никаких дополнительных копирований, содержимое Option проверили один раз, модифицировали дважды. Что именно с вектором не так? Да, проверки на выход за границу - это дополнительный оверхед. Но в практических случаях, компилятор довольно часто их может выкинуть и выкидывает.

Постойте, вы сами себе противоречите. Выше был упомянут swap, в который завтра кто-то может попробовать передать ссылки на один и тот же объект через unsafe(и получить то самое UB). Но swap почему-то безопасен, а optional без проверки нет.

В safe rust невозможно создать две мутабельные ссылки на один и тот же объект (и даже одну мутабельную, и ещё одну). Если вы в рамках unsafe Rust сделали две мутабельные ссылки на одну и ту же память и вернули их в safe - комилятор дальше уже ничего не гарантирует, к сожалению. Абсолютно так же можно написать unsafe функцию, которая работает с Option<NotNull> без проверки на Some. Эту функцию, после этого, нужно просто называть тоже unsafe (и иметь дополнительный инвариант, который проверять программистом, а не компилятором).

Ещё раз: нельзя забыть написать .value() - это так же не скомпилируется, поскольку типы будут разные. Если же вы забываете таким образом, что вместо .value() пишете *(это какая-то очень странная форма забывчивости)

При переписывании функции с входного типа &T на Optional<T>, например, можно случайно оставить звёздочку. А в расте нельзя - не скомпилируется.

Не просто безопасные, а безопасные и дешевые.

Это так же не идея Rust. И даже не единственная реализация. И даже не первая - всё это уже есть в том же C++.

https://benchmarksgame-team.pages.debian.net/benchmarksgame/performance/spectralnorm.html - игрушечная задача, в который лучший код на раст так же хорош, как и лучший на c++ . При этом не содержит ни одной unsafe строчки.

С benchmarksgame интересная история. Там кто-то засылал код на C++, который оказывался быстрее всего остального, но его удалили, поменяв требования. Насчёт unsafe - оно там есть, конечно же - в библиотечном коде. Без unsafe на Rust писать всё же нельзя.

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

Не дочитал вопрос. Я привёл примерный аналог тому swap на Rust. restrict там не нужен не потому, что это будет работать и с перекрывающимися областями памяти, а потому как компилятор сам всё поймёт. Очевидно, если области перекрываются нужно проверять.

Я не для этого писал про unsafe в swap. Я говорил о невозможности выразить на safe Rust элементарную операцию.

Программа на safe rust во многих случая работает on par с аналогичной программой на C/C++.

За счёт unsafe - да, сравнимый уровень получается. Но о безопасности/надёжности ПО на Rust в таком случае речи идти не может.

Нет, не нужны никакие копирования, зачем?

Да, я неверно сказал.

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

Да, небесплатная проверка границ. Выкинет оно только в простых случаях(примерно в тех же самых, в которых в C++ получим предупреждение о выходе за границу). Хотим убрать тормоза - пишем unsafe.

Если вы в рамках unsafe Rust сделали две мутабельные ссылки на одну и ту же память и вернули их в safe - комилятор дальше уже ничего не гарантирует, к сожалению.

Вот эта ситуация, да. Снова всё нужно руками проверять. Как и с optional без предварительного if.

При переписывании функции с входного типа &T на Optional<T>, например, можно случайно оставить звёздочку. А в расте нельзя - не скомпилируется.

Всё равно не пройдёт. * от T(или T& - неважно) даст другой тип(условно, U&, U != T), * на optional<T> даст T&.

Это так же не идея Rust. И даже не единственная реализация. И даже не первая - всё это уже есть в том же C++.

Вопрос уровня гарантий. Для соблюдения гарантий safe Rust, весь unsafe код должен быть написан аккуратно. Например, safe Rust заприщает алиасинг (т.е. после завершения unsafe кода не должно получаться две мутабельные ссылки на одну область памяти). Раст - это один из быстрых языков с неожиданными сильными гарантиями. Кроме проверки типов, которую уже обсуждали, есть, например, ещё лайфтаймы.

Я не для этого писал про unsafe в swap. Я говорил о невозможности выразить на safe Rust элементарную операцию.

В safe rust эта операция элементарно выражается: std:mem:swap(...). Собственно, пример безопасной абстракции, написанной на опасном rust. Она один раз написана, теперь ей можно пользоваться.

Насчёт unsafe - оно там есть, конечно же - в библиотечном коде. Без unsafe на Rust писать всё же нельзя.

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

За счёт unsafe - да, сравнимый уровень получается. Но о безопасности/надёжности ПО на Rust в таком случае речи идти не может.

Довольно часто достаточно пользоваться safe-абстракциями. Например, от stdlib. И даже в stdlib подавляющая часть кода - safe.

Да, небесплатная проверка границ. Выкинет оно только в простых случаях(примерно в тех же самых, в которых в C++ получим предупреждение о выходе за границу). Хотим убрать тормоза - пишем unsafe.

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

Вот эта ситуация, да. Снова всё нужно руками проверять. Как и с optional без предварительного if.

Писать unsafe код на расте сложнее, чем просто код на C++. К сожалению, формального описания "какие же инварианты на самом деле нужно поддерживать, выходя из safe", нет.
В любом случае, это какой-то повод более чётко понять как выглядят ограничения на входные данные. Например, поменять (Option<A>, Option<B>) на Option<(A,B)>, если известно, что они оба одновременно представлены или даже Option<(A, Option<B>)>, если второй не бывает представлен без первого (для массивов алгебраичиских типов уже не хватает, да).

Вопрос уровня гарантий. Для соблюдения гарантий safe Rust, весь unsafe код должен быть написан аккуратно.

Как было показано выше, Rust не даёт более сильных гарантий относительно других ЯП. C++ предоставляет те же гарантии - optional::value, vector::at, unique_ptr(raw pointer в safe rust использоваться не могут), нетривиальные лайфтаймы в обоих случаях руками пишутся. Более явное разделение - да, какой-то иной уровень - нет.

Например, safe Rust заприщает алиасинг (т.е. после завершения unsafe кода не должно получаться две мутабельные ссылки на одну область памяти).

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

В safe rust эта операция элементарно выражается: std:mem:swap(...).
Собственно, пример безопасной абстракции, написанной на опасном rust. Она один раз написана, теперь ей можно пользоваться.

В safe Rust выражается не swap, а вызов unsafe кода. Опять же, оно везде так. Точнее, на Rust выражается даже swap, но в варианте с временной копией объекта - большие объекты так свопнуть не получится(места на стеке не хватит).

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

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

Довольно часто достаточно пользоваться safe-абстракциями. Например, от stdlib. И даже в stdlib подавляющая часть кода - safe.

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

Вот так делать нехорошо - возникает проблема с границей unsafe: написав unsafe вы гарантируете инвариант.

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

К сожалению, формального описания "какие же инварианты на самом деле нужно поддерживать, выходя из safe", нет.

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

Как было показано выше, Rust не даёт более сильных гарантий относительно других ЯП. C++ предоставляет те же гарантии - optional::value, vector::at, unique_ptr(raw pointer в safe rust использоваться не могут), нетривиальные лайфтаймы в обоих случаях руками пишутся. Более явное разделение - да, какой-то иной уровень - нет.

Unsafe - почти не даёт дополнительных гарантий безопасности. А вот safe rust - даёт.

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

У любого языка программирования там где-то глубоко внизу unsafe.

Чужие безопасные абстракции, написанные на unsafe rust - это то самое свойство Раста (быстрые безопасные абстракции). В предположении, что unsafe часть написана корректно, остальной код уже не может привести к широкому классу ошибок. Safe rust поверх стандартной библиотеки, кажется, концептуально не отличается от питона: где-то внизу есть "опасный" код, но любая программа, которая компилируется не может содержать, например, двойного освобождения памяти.

Безопасный двусвязанный список уже есть реализованный. Да, концепция владения раста довольно плохо ложится на структуры с циклическими ссылками. В safe rust это тоже можно реализовать (с помощью Rc или Weak), но будут проблемы с перформансом. А в unsafe rust - можно, но тоже довольно сложно, чтобы интерфейс был безопасным.

В safe Rust выражается не swap, а вызов unsafe кода. Опять же, оно везде так. Точнее, на Rust выражается даже swap, но в варианте с временной копией объекта - большие объекты так свопнуть не получится(места на стеке не хватит).

Подождите, если я делаю std:mem:swap, то выражается ровно тот swap, который нужен. И выражается он в safe rust (потому что эта функция стандартной библиотеки помечена безопасной).

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

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

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

Кажется, мы в этом месте ходим кругами. Идея (и судя по benchmarksgame довольно успешная) заключается в том, что на безопасном расте можно получить довольно производительный и быстрый код. Да, для этого нужен фундамент (написанный на unsafe rust, да он есть не везде), там где он есть - поверх него можно написать быстрое и безопасное приложение.
При этом есть возможность этот фундамент расширить. Для кода, расширяющего фундамент (unsafe), требований больше, чем в C++. Зато результат получается более сильным - если получилось сделать безопасную абстракцию (и реализовать её без ошибок), то можно писать безопасный код, у которого много гарантий проверяется компилятором.

А вот safe rust - даёт.

Повторяемся. Я уже показал эквиваленты в C++, предоставляющие те же гарантии.

Чужие безопасные абстракции, написанные на unsafe rust - это то самое свойство Раста (быстрые безопасные абстракции). В предположении, что unsafe часть написана корректно, остальной код уже не может привести к широкому классу ошибок. Safe rust поверх стандартной библиотеки, кажется, концептуально не отличается от питона: где-то внизу есть "опасный" код, но любая программа, которая компилируется не может содержать, например, двойного освобождения памяти.

Вы опять повторяетесь. Это везде так. И ещё, по поводу 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.

Подождите, если я делаю std:mem:swap, то выражается ровно тот swap, который нужен. И выражается он в safe rust (потому что эта функция стандартной библиотеки помечена безопасной).

Нет. Выразить - значит реализовать в данном случае. Использовать проблем никаких, это понятно. Но реализуется на safe Rust только неэффективный аналог, поэтому снова unsafe.

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

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

Кажется, мы в этом месте ходим кругами.

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

Повторяемся. Я уже показал эквиваленты в C++, предоставляющие те же гарантии.

Гарантии safe rust выше, чем гарантии C++ (даже с линтером). Например вот здесь обсуждалось, что даже мощные линтеры к С++ не отвергают некорректный код, который отвергает раст (да, Раст иногда отвергает корректный код, корректность которого он не может доказать. По теореме Райса, если ты отвергаешь весь некорректный код, то обязан отвергать и какой-то корректный).

Гарантии unsafe rust действительно близки к гарантиям C++ (чуть получше с арифметикой, зато программисту нужно ещё поддерживать инварианты алиасинга на границе unsafe).

UFO just landed and posted this here

Разрешите, начну с конца.

И да, повторяю. Раст - это кусок фронта к С++-компилятору. Он в принципе не может делать того, что не может С++-компилятор.

Вот это очевидно не правда. Верно, что для любого кода на Rust можно написать код на LLVM, который делает то же самое. Линтеры к C++ вообще пользуются компилятором. Следует ли из этого, что они в принципе не могут делать того, чего не делает компилятор? (И, например, анализом потока исполнения в линтере не находят больше ошибок, чем gcc/clang?)

С++ же работает по другой модели. Он как раз таки ищет некорректный код и отвергает. Хотя некоторые пытаются внедристь, в том числе, и модель раста. Т.е. все же инварианты туда уже завезли - этим занимается ms.

Да, в безопасном растре есть модель владения памятью, которую он навязывает (например, никаких других ссылок на объект, если на него есть мутабельная ссылка). Да, она не общая (в том смысле, что есть корректные программы, которые используют другую моделью владения памятью). Если Вы в рамках unsafe, например, получили вторую мутабельную ссылку на объект, то да, safe rust никаких гарантий уже не даёт.

С++ же работает по другой модели. Он как раз таки ищет некорректный код и отвергает. Хотя некоторые пытаются внедристь, в том числе, и модель раста. Т.е. все же инварианты туда уже завезли - этим занимается ms.

C++ довольно часто не отвергает некорректный код, а спокойно его компилирует в UB.

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

Раст отвергает код, в котором нарушены инварианты. И шаринг не запрещён - на один объект может быть сколько угодно ссылок. Запрещает мутабельный шаринг - если есть хотя бы одна мутабельная ссылка, то больше ссылок быть не должно. Это позволяет, например, бороться с гонками данных.

Эти пару инвариантов работают только когда, когда у нас нет шаринга, либо организация памяти не примитивная. Далее все "гарантии" перетекают в рантайм с оверхедом, т.е. в ансейф. И уже никаких гарантий нет.

Подождите. Либо перетекают в safe с оверхедом (как linked list построенный на Option<Rc<RefCell<T>>>), либо перетекают в safe абстракцию, написанную на unsafe rust (как swap или std:collections:linkedlist), либо модель памяти safe rust не совместима с вашим алгоритмом (и тогда использовать для реализации вашего алгоритма раст - это плохая идея).

И даже если мы каким-то чудом предположим, что все любы не дырявые, то оверхед никуда не делся.

Option<&T>, например, реализован без оверхеда - известно, что ссылка не бывает нулевой, в результате вся структура по размеру занимает ровно ссылку (со специальным значением 0 для None). Как мы уже обсуждали выше, swap так же работает совсем без оверхеда. Вообще довольно большАя часть абстракций, представленных стандартной библиотекой, содержит небольшой оверхед, если содержат.

UFO just landed and posted this here

Вы опять повторяетесь. Это везде так. И ещё, по поводу double-free: http://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=rust

В Unsafe rust можно сделать double free. Вроде, никто этого не отрицал.

Нет. Выразить - значит реализовать в данном случае. Использовать проблем никаких, это понятно. Но реализуется на safe Rust только неэффективный аналог, поэтому снова unsafe.

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

Да, именно так. Любые програмные примитивы - это машкод в другой нотации (или, наоборот, любые програмные примитивы, включая C++ - это не машкод, компиляция с оптимизацией может много всего переставить или даже векторизовать). В safe rust swap - это програмный примитив с примерно нулевой дополнительной стоимостью (при компиляции). Что и записывалось в преимущества (и откуда началась эта ветка):

Как раз идея Раста в том, чтобы предоставить дешёвые (в идеале, с нулевой стоимостью) абстракции, которые при этом безопасны.

UFO just landed and posted this here

У safe rust есть гарантии от компилятора. У unsafe rust - гарантий от компилятора гораздо меньше (и так же как в C# unsafe может сломать выполнение где-то дальше, а не в моменте вызова unsafe).

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

В тот момент, когда нужна арифметика указателей (а точнее разыменование указателя, полученного арифметическим действием) - нужен unsafe. Примерно потому что компилятор в compile time не может проверить (в общем случае), что в коде должно быть именно написанное вами

template<typename T> void swap(T& __restrict l, T& __restrict r) {
    for (auto pl{(char*)&l}, pr{(char*)&r}, epl{pl + sizeof(T)}; pl != epl; ++pl, ++pr) {
        auto c{*pl};
        *pl = *pr;
        *pr = c;
    }
}

, а не pl <= epl (вместо !=) во второй строке. Формально проверять границы в compile time на голых указателях невозможно (и в итоге появляютсе оверхеды, если это делать каждый раз).

Оверхеда нет, потому что у нас С++-компилятор, который этот позор свернёт, возможно.

Там последовательность нескольких функций с дерективой inline. Компилятор отдельно очень попросили всё свернуть вместе.

Когда вдруг внешний оптимизатор стал частью семантики языка? Я понимаю, что удобно всегда прятаться за llvm, но мы ведь говорить о гарантиях? Мы говорим о расте. llvm стал частью раста? Инлайнинг стал частью раста? Нет и нет.

Во-первых, в какой момент быстродействие стало частью семантики языка? gcc разных версий (или gcc и clang) компилирует один и тот же код (написанный на C++11, например) в разные программы с разной производительностью. Значит ли это, что между релизами gcc у языка поменялась семантика?
Во-вторых, оптимизации llvm, полезные для раста, разработчики раста тоже делают (и их вклад в llvm ненулевой).

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
Про .value() уже сказали. *x — это такая же ансейф-операция. При этом .value() — это не .unwrap(). Первое позволяет ловить ошибки — второе нет. Поймать это, конечно же можно, потому как паники это С++-исключения из llvm, но нормальных средств для этого нет.

Можно раскрыть мысль? Почему unwrap не позволяет "ловить ошибки"? Что значит "нет нормальных средств" для перехвата паники?

UFO just landed and posted this here
Вы мне покажете try/catch и прочее в расте?

Покажу panic::catch_unwind, но боюсь начнутся разговоры, что это "костыли", что мягко говоря, передёргивание. Так как "не рекомендуется" использовать по двум причинам: первая — это то, что в языке подталкивают делать обработку ошибок на Result вместо паники и второе — на уровне бинаря можно выбрать стратегию обработки паники. Но если мы пишем не библиотеку, то это не проблема.


Аборт перехватить нельзя.

Было бы очень странно будь оно иначе: сначала включаем panic = abort, чтобы выкинуть код размотки стека, а потом удивляемся.


Что такое нормальные средства я написал выше.

Ясно. Что ж про "ворованную скриптуху" ничего не было? Тогда можно было бы время не тратить.

UFO just landed and posted this here

Я уверен, что я скажу некую глупость, но мне как самому обычному разработчику есть вопрос, а почему я должен ловить исключения через try/catch а не обрабатывать сразу ошибки через Result как это вроде сделано в rust?
Я не знаю C++, работаю только с C# и мне непонятна такая агрессия по отношению к rust, хотя вроде даже большие игроки им заинтересовались...

UFO just landed and posted this here

По поводу "больших игроков" - никакой заинтересованности нет. Это всё сказки. Всеми языками кто-то заинтересован. 

Но извольте, разве внесения кода на языке Rust (ну или на интерпретаторе llvm, как вам будет удобнее) в ядро linux, как говориться "наравне" с C не является интересом к нему? Причём судя по всему продвигал это Google ссылаясь на то, что это сократит число ошибок памяти.
Мы все знаем про "кладбище идей" Google, но получается Rust теперь с нами надолго.

UFO just landed and posted this here

Если вам достаточно result, то почему существует unwrap? Как раз таки потому, что у result есть проблемы.

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

Но иногда обработка ошибок не нужна. Бывает достаточно того, чтобы программа просто завершалась в случае ошибки с указанием места, где ошибка произошла, выдав стандартный стектрейс. Удобно во время разработки, пока ещё далеко до окончательной production-ready версии кода.

Или же, бывает так, что разработчик гарантирует инварианты, инкапсулируя их где-то в приватных методах, так же, как это делается с unsafe, в то время как в интерфейсе где-то под капотом присутствует обязательный Option/Result. Например, какое-то поле всегда Some до вызова Drop::drop, а после вызова drop - к нему никто уже не обратится.

unwrap() решает эти вопросы.

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

В отличие от некорректной работы с указателями, с которыми сравнивается тут где-то выше в обсуждении - Result/Option с unwrap() не создаёт UB и помогает отслживать ошибки.

> У меня нет агрессии - агрессия есть у его адептов, которые именно что агрессивно его продвигают.

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

UFO just landed and posted this here

И вот "белый человек" напишет throw, а адепт раста что напишет? Не result же позорный. Поэтому он так же пишет throw, ой, unwrap.

Вообще то throw (в виде panic!) он тоже вполне написать может. Если нужна паника - можно никакого unwrap не делать.

В С++ есть raii, которое не может существовать без исключений. Раст тащил raii из С++, а вместе с ним утащил и исключения без которых это не работает.

Зачем для raii (в растовском варианте) нужны обрабатываемые исключения? Зачем нужна какая-то обработка паники (типа выполнения деструкторов на raii) - понятно. Но зачем нужна абстракция ловли паники?

UFO just landed and posted this here

Исключения этой проблемы лишены. У нас там стоит уже обработчик. И наше приложение не падает.

При наличии обработчика выше передавать наверх result, а не паниковать, вроде ничем не плохо.

Аналогичная проблема есть у ?. Он не работает в общем случае. Мы не можем расширить закрытый тип ошибки. Раст не позволяет мержить закрытые типы.

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

Раст-адепты придумали ахренительные истории, что это ассерты. Что проверять ничего ненужно. Нет, проверять нужно. Просто вы не можете это сделать, а всё остальное оправдания.

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

И я был бы не против - живите как знаете, но они приходят и рассказывают, что у них нет проблем

У них есть другие проблемы. Например, как написать unsafe rust так, чтобы не было проблем с алиасингом.

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

Поэтому сейчас вся инфраструктура раст пользуется для таких ошибок неявной обработкой через механизм исключений (паника + catch). А result используют для ошибок связанных с высокоуровневой бизнес логикой.

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

Я бы в этом месте ожидал разделение библиотек. Если есть высокоуровневая бизнес-логика, то, вроде бы, catch почти никогда не нужен (с практической точки зрения) - если у тебя нет памяти и try_push вернулся с ошибкой из-за нехватки памяти, то у тебя очень ограниченное пространство вариантов (по большому счёту, ровно упасть). А вот если ты что-то делаешь на более низком уровне - Result оказывается хорошим вариантом, я бы ожидал, что там станет больше функций вида try_something.

UFO just landed and posted this here
Нужен, к тому же это не единственное место, которое паникует. Тысячи их. Любое создание ришного объекта.

struct Dropping;

impl Drop for Dropping {
    fn drop(&mut self) {
        println!("Dropped");
    }
}

impl Dropping {
    fn new() -> Self {
        Self
    }
}

Объект. RAII-шный. И где тут паника при создании? Тело Dropping::new вообще состоит из единственной конструкции ret.

UFO just landed and posted this here

То есть таки не любое создание RAII-шного объекта — паника.

UFO just landed and posted this here

Ну и, поздравляю, вы сделали тот самый dyn. А если там будет не только строка - там будет ещё и box. А ну и ещё поглумились над двумя базовыми раст-методичками. Это "тайпсейфити при result" и "неявные преобразования типов - зло".

Нету там 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 a Result, and is equivalent to a match expression, where the Err(err) branch expands to an early Err(From::from(err)), and the Ok(ok) branch expands to an ok expression"

В языке с исключениями я просто пишу throw. Без всего этого страха.

А потом ловите не то исключение, которое ожидали.

Кстати, расскажу интересную историю. Буквально недавно я практически один отстаивал тезис ~"обрабатывать null их malloc не имеет смысла". И очень много популярный раст-адептов со мною спорили, доказывали мне обратное.

Обрабатывать null в malloc имеет смысл, чтобы сразу упасть и не перепелить случайно память от момента получения этого null до падения (и не закрывать ресурсы с распиленной памятью). Или вы обсуждали что-то другое?

Никак. Весь раст существует на фейковом БЧ. Если бы они могли хоть каким-то образом сделать хоть какой-нибудь шаринг - они бы его сделали.

RO шаринг в расте есть и хорошо работает. RW тоже есть, но там появляется оверхед в рантайме.

UFO just landed and posted this here

Нет, раст не может его выразить. Это невозможно. Это всё дырявые тормозные ансейф-костыли. Показывайте мне шаринг целиком на safe rust.

Пожалуйста: https://play.rust-lang.org/?version=stable&mode=release&edition=2018&gist=5d2ff84acad55af5c852bda8b5761018
Да, там arc, но это safe-костыль (альтернативно можно сделать этот mutex глобальным).

Раст тащил raii из С++, а вместе с ним утащил и исключения без которых это не работает.

Почему RAII не работает без исключений? Да даже в С RAII было бы полезно (и существует в виде cleanup атрибута).

Это мусорный костыль.

"Мусорный костыль" потому что ты так написал? Это не так работает.


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

"Многословно" и "неуюзабельно" — это разные вещи. Возможность перехватить и обработать панику есть. Да, не так удобно как catch, но это как раз потому что в языке подталкивают не использовать панику для обработки ошибок. К слову, пишу на расте больше четырёх лет и carch_unwind приходилось использовать буквально пару раз, причём большая часть из них — в байндингах. А уж матчиться по типу паники так и вовсе пришлось аж один раз — в тесте.


Чему удивляется? Половина методичек призывает использовать panic = abort, либо писать его сразу, а не паниковать.

Можно хотя бы несколько примеров? Не припоминаю такого в основных учебных материалах.


О чём по ссылке написано явно:

По ссылке написано ровно о том, о чём я только что сказал. И предупреждение относится к авторам библиотек, которые действительно не могут полагаться на конкретную стратегию обработки паники. При разработке приложений таких ограничений нет. Впрочем, мне попадались и библиотеки, которые требовали panic = unwind.

UFO just landed and posted this here

Зачем там ансейф?

Где именно?

В вызове unsafe-функции? - потому что это также unsafe действие.

В определении unsafe-функции? Потому что она ведёт к UB, если вызывающий не соблюл инвариант (неалиасность указателей).

Зачем в реализации свапа указатели?

Потому что существует реализация свапа для указателей, которая умеет делать это очень быстро при условии соблюдения инварианта. Этот инвариант гарантирован для &mut T, а, значит, нет необходимости делать повторную реализацию для него, когда можно воспользоваться той.

Вы этим примером только подтвердили тезис выше. Не способность языка выразить даже свап. В чём была проблема написать `tmp = a; a = b; b = tmp;`, как это написано в том же C++?

Почему же вы считаете, что это нельзя выразить формально на Расте?

От чего же нельзя? Можно:

fn swap<T: Clone>(l: &mut T, r: &mut T) {
    let tmp = r.clone();
    *r = l.clone();
    *l = tmp;
}

Но это та самая наивная и неэффективная реализация, от которой мы старались уйти. Причём, ограничение в виде реализации Clone присутствует и в C++ версии, а именно требование на copy- и move-assignable (надеюсь, cppreference будет достаточно корректным источником тут).

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

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

Что же касается тезиса о том, что стандартная библиотека написана с большим количеством unsafe, то в чём именно проблема этого? В Расте есть unsafe и никто этого не отрицает, но идея в том, что в клиентском коде его прекрасно получается избегать.

Иначе (далее утрированный пример) можно точно так же сказать, что почему-то выразить средствами C++ (практически любого ЯП, на самом деле) сложение двух целых чисел можно, но это будет очень медленно (цикл (который, как и случай со swap, может сам требовать некоторый более примитивный аналог самого себя, в данном случае для счётчика цикла)) либо небезопасно (машинно-специфичный АСМ с учётом размерности, эндианности etc, причём это ещё если данный ЯП такой инструмент даёт). А то, что для этого есть "особый оператор +" считать признаком слабости языка.

Или же говорить, что использование инлайн-АСМа это признак слабости языка.

У нас там ссылки. Зачем нам ссылки, какие-то гарантии, если после они никак не используются?

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

Нет, unreachable_unchecked нет никакого смысла быть ансейф. Просто это взятая из ллвм/С++-семантика(builtin_unreachable) которая продуцирует уб.

Показанный далее пример так же builtin_unreachable-семантики, которому никак не требуется быть ансейф. unreachable не ансей ф.

Почему же нет никакого смысла? Это хинт компилятору (не важно, какой там бэкенд, LLVM, GCC, whatever), что данная ветка гарантированно (разработчиком) недостижима, и компилятор (снова же, я не ссылаюсь на какой-то конкретный, а говорю, в общем, что значит эта (псевдо)функция) имеет право рассматривать это, как истину в последней инстанции. Несоблюдение этого инварианта есть UB, и не потому, что "так где-то сделано", а потому что вся суть этой функции в том, чтобы дать строгую статически-недоказуемую гарантию. Отсутствие unsafe (то есть, UB при несоблюдении инварианта) значило бы, что даже если ветка была бы достигнута, она должна была бы как-то конкретно быть обработана, для чего и существует не-unsafe аналог, который паникует (можно провести аналогию с плюсовым исключением) при достижении.

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

Конкретно в данном примере корректность условной деконструкции.

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

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

Причём, опять же, что плохого в том, что семантика примитивная? Она простая, и при этом позволяет чётко описать действие - это же (синтаксис) и входит в понятие инструментария языка, о котором шла речь изначально.

А что до бесплатности, то куда ему быть ещё бесплатнее? Это пара проверка+доставание, которая в том же C++ (да и, в целом, любом ЯП) одинаково дорогая. Причём тут, поскольку это часть синтаксиса языка, очевидно, что это одно из первых мест, которые оптимизируются во всю (относительно, условно, каких-то конкретных функций).

Нормально и как раз таки на уровне компилятора это реализовано в том же ts. Когда у нас есть полноценный type refinement, а не какая-то его случайная эмуляция.

Оно позволяет делать и if(!x) {}, позволяет делать if(x && y) и прочее. let if ничего этого не позволяет, либо надо костыль обёртки.

А что именно тут не так? Это и не type refinement, формально, а просто pattern matching. Хотя играет он тут ту же роль: ограничивает в пределах блока if множество значений value типом T (сахара ради, новая переменная внутри if-let может, иметь точно такое же название, как и изначальная переменная, а ля if let Some(value) = value).

Про .value() уже сказали. *x - это такая же ансейф-операция.

В том то и дело, что в C++ в абсолютно изначально корректном месте приходится использовать unsafe (иначе говоря, UB-провоцирующую) функцию тогда, когда она таковой не является. Вся причина в том, что нету той самой пары проверь+возьми одним "атомарным" выражением, и система типов действительно делает тот UB синтаксиески возможным.

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

Вы выше, если не ошибаюсь, писали, что Раст не даёт ничего для безопасности с нулевой ценой, но ведь тут именно оно - операция, которая в C++ описывалась парой никак не связанных statement'ов, тут представлена одним, при этом не оставляя места даже для формального UB.

Поймать это, конечно же можно, потому как паники это С++-исключения из llvm, но нормальных средств для этого нет.

Суть паник именно в том и состоит, чтобы сообщить о том, что какой-то хрупкий (=недоказуемый статически) инвариант был нарушен, не провоцируя при этом 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 является небезопасным.

Кто начинает учить Rust - могут быть полезны уроки на ютубе, канал D-web ​

Коллеги здравствуйте. Хочу попробовать Rust (на работе). Но проблема в том, что у нас закрытая сеть, машины разработчиков под Ubuntu. Из внешней сети пробовать работать не вариант - совсем игрушечные проекты делать не хочется, pet-project на данный момент нет.

Так вот подскажите как настроить в такой ситуации работу с Rust (ну или хотя бы в какую сторону гуглить для начала)?
Может быть делают *deb пакеты каких-то стабильных выпусков (а что со сторонними библиотеками тогда и как их обновлять)? Или ещё какой механизм?

rustc (и 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#57337200

Спасибо.

А получить локальный репозиторий внешних пакетов (выкачать всё, упомянутое в crates.io, желательно ещё и взаимно совместимых версий (по формальным признакам версий пакетов, транзитивно вниз по дереву зависимостей)) как-то можно?

Может быть cargo (или какая-то другая внешняя утилита) уже делает такие оффлайн-репозитории всех пакетов?

выкачать всё, упомянутое в crates.io

Прям вообще всё с crates.io? Тут не подскажу, но если понятно какие зависимости нужны, то есть cargo vendor. У нас на проекте именно оно и используется. То есть, добавление или обновление зависимостей проходит через определённую процедуру, код зависимостей выкачивается при помощи этой команды и дальше для сборки интернет не нужен.

Возможно, вам поможет инструмент создания приватных registry - meuse, в списке фич заявлено зеркалирование crates.io.

Sign up to leave a comment.

Articles