Исключения этой проблемы лишены. У нас там стоит уже обработчик. И наше приложение не падает.
При наличии обработчика выше передавать наверх result, а не паниковать, вроде ничем не плохо.
Аналогичная проблема есть у ?. Он не работает в общем случае. Мы не можем расширить закрытый тип ошибки. Раст не позволяет мержить закрытые типы.
? позволяет привести тип ошибки к тому типу, который нужен функции по сигнатуре. Минимальный пример.
Раст-адепты придумали ахренительные истории, что это ассерты. Что проверять ничего ненужно. Нет, проверять нужно. Просто вы не можете это сделать, а всё остальное оправдания.
В не-ядерном коде можно сделать очень немного, если попытка push закончилась ошибкой нехватки памяти. Я бы ожидал, что в тех местах, где разумные действия есть сейчас (или появятся в будущем) - интерфейсы поменяются, будут как раз result.
И я был бы не против - живите как знаете, но они приходят и рассказывают, что у них нет проблем
У них есть другие проблемы. Например, как написать unsafe rust так, чтобы не было проблем с алиасингом.
Если бы не two compelemnt был бы багом, то все оптимизации компилятора, которые завязаны на не-переполнение signed int, были бы багом. Вообще, "переполнение знаковых - это undefined behaviour" - это фича языка, а не бага - оптимизировать код, в котором программист задумался и избежал можно лучше (и компиляторы это делают). Другое дело, что на результат работы программиста это накладывает дополнительные ограничения.
Так и нет ответа почему вдруг всё свелось к stdlib.
Утверждение "Компилятор гарантирует, что если stdlib написан корректно, то safe код, написанный поверх stdlib, не приведёт, например, к разименованию нулевого указателя." абсолютно прозрачно меняется, если поменять stdlib на другой источник unsafe кода. "Компилятор гарантирует, что если весь unsafe код (включая код зависимостей) написан корректно, то safe программа не вызовет разыменование нулевого указателя".
У safe rust есть гарантии от компилятора. У unsafe rust - гарантий от компилятора гораздо меньше (и так же как в C# unsafe может сломать выполнение где-то дальше, а не в моменте вызова unsafe).
Переведу на нормальный язык. safe-раст настолько немощный, что не может выразить свап. Пришлось сувать ансейф туда, где его быть не должно.
В тот момент, когда нужна арифметика указателей (а точнее разыменование указателя, полученного арифметическим действием) - нужен unsafe. Примерно потому что компилятор в compile time не может проверить (в общем случае), что в коде должно быть именно написанное вами
, а не pl <= epl (вместо !=) во второй строке. Формально проверять границы в compile time на голых указателях невозможно (и в итоге появляютсе оверхеды, если это делать каждый раз).
Оверхеда нет, потому что у нас С++-компилятор, который этот позор свернёт, возможно.
Там последовательность нескольких функций с дерективой inline. Компилятор отдельно очень попросили всё свернуть вместе.
Когда вдруг внешний оптимизатор стал частью семантики языка? Я понимаю, что удобно всегда прятаться за llvm, но мы ведь говорить о гарантиях? Мы говорим о расте. llvm стал частью раста? Инлайнинг стал частью раста? Нет и нет.
Во-первых, в какой момент быстродействие стало частью семантики языка? gcc разных версий (или gcc и clang) компилирует один и тот же код (написанный на C++11, например) в разные программы с разной производительностью. Значит ли это, что между релизами gcc у языка поменялась семантика? Во-вторых, оптимизации llvm, полезные для раста, разработчики раста тоже делают (и их вклад в llvm ненулевой).
И да, повторяю. Раст - это кусок фронта к С++-компилятору. Он в принципе не может делать того, что не может С++-компилятор.
Вот это очевидно не правда. Верно, что для любого кода на 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 так же работает совсем без оверхеда. Вообще довольно большАя часть абстракций, представленных стандартной библиотекой, содержит небольшой оверхед, если содержат.
ПО пишут люди. Даже в стандартной библиотеке и компиляторе любого из современных языков регулярно находят ошибки. Поэтому писать "если совсем нет ошибок, то ..." - это слишком сильное утверждение.
И да, ошибки в другом языке нормально, потому что он нигде говорит о том, что гарантирует что-то там. С растом такое не работает. Либо мы принимаем то, что для любой лоулевел либы ничего не гарантируется, в том числе и для stdlib.
Для stdlib (и любой другой библиотеки использующей unsafe) сам компилятор корректность гарантировать не может. Так же как с C# unsafe - можно выстрелить себе в ногу в unsafe блоке, а упадёт само приложение где-то дальше. Но никто не мешает написать быструю библиотеку для чего-то своего поверх stdlib. Как раз в силу того, что абстракции дешёвые во время исполнения (и компиляции) она будет достаточно близка к low-level. Да, если нужна работа с железом (на уровне регистров, например), придётся писать какой-то unsafe код (или пользоваться другой библиотекой, которая предоставляет безопасную абстракцию поверх этих регистром).
В Unsafe rust можно сделать double free. Вроде, никто этого не отрицал.
Нет. Выразить - значит реализовать в данном случае. Использовать проблем никаких, это понятно. Но реализуется на safe Rust только неэффективный аналог, поэтому снова unsafe.
Обобщение не верное, конечно, но в контексте swap мысль ясна. Присвоение байта - это примитив железа, а не библиотеки или языка. Очевидно, без железа код работать не сможет. Т. е. я просто использовал машкод в другой нотации. В противовес этому - в Rust swap является программным примитивом.
Да, именно так. Любые програмные примитивы - это машкод в другой нотации (или, наоборот, любые програмные примитивы, включая C++ - это не машкод, компиляция с оптимизацией может много всего переставить или даже векторизовать). В safe rust swap - это програмный примитив с примерно нулевой дополнительной стоимостью (при компиляции). Что и записывалось в преимущества (и откуда началась эта ветка):
Как раз идея Раста в том, чтобы предоставить дешёвые (в идеале, с нулевой стоимостью) абстракции, которые при этом безопасны.
Повторяемся. Я уже показал эквиваленты в C++, предоставляющие те же гарантии.
Гарантии safe rust выше, чем гарантии C++ (даже с линтером). Например вот здесь обсуждалось, что даже мощные линтеры к С++ не отвергают некорректный код, который отвергает раст (да, Раст иногда отвергает корректный код, корректность которого он не может доказать. По теореме Райса, если ты отвергаешь весь некорректный код, то обязан отвергать и какой-то корректный).
Гарантии unsafe rust действительно близки к гарантиям C++ (чуть получше с арифметикой, зато программисту нужно ещё поддерживать инварианты алиасинга на границе unsafe).
Компилятор гарантирует, что если stdlib написан корректно, то safe код, написанный поверх stdlib, не приведёт, например, к разименованию нулевого указателя.
Да, предположение о корректности является довольно сильным. Но если стандартная библиотека содержит серьёзные ошибки, то на любом языке программирования у Вас проблемы.
Как было показано выше, 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++. Зато результат получается более сильным - если получилось сделать безопасную абстракцию (и реализовать её без ошибок), то можно писать безопасный код, у которого много гарантий проверяется компилятором.
Это так же не идея 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>)>, если второй не бывает представлен без первого (для массивов алгебраичиских типов уже не хватает, да).
Как интересно вы проигнорировали часть моего сообщения. Спрошу ещё раз: что насчёт идеи о безопасных абстракциях? Почему предыдущий комментатор написал "это идея 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>, например, можно случайно оставить звёздочку. А в расте нельзя - не скомпилируется.
Это тривиальная операция, оверхэда здесь быть не может. Кстати, примечательно что даже такой простой код написан полностью в unsafe.
Как правильно безопасно реализовывать swap без дополнительной памяти и внешней гарантии на то, что объекты не пересекаются по памяти?
Что нового вы здесь сообщили? Такое есть где угодно, даже в том же питоне и прочих(там сишный код, вызываемый из питона == unsafe в Rust). Про языки помощнее типа C++ и говорить не стоит. Снова выдаёте чужое за своё.
Вопрос в скорости абстракции. Написанная на питоне программа будет (почти всегда) работать существенно медленнее, чем программа, написанная на C++. Rust-абстракции в этом смысле гораздо более быстрые.
Во первых, проверять нужно не всегда, но safe Rust не может не проверять - эффективности уже нет
Если вы проверили это уже где-то раньше - поменяйте тип с Option<T> на T, после этого проверять не нужно. Если данные приходят в функцию в виде Option<T>, то проверять нужно (даже если сегодня мы знаем, что там всегда что-то есть, то завтра кто-то может попробовать переиспользовать эту функцию по её сигнатуре и передаст туда Empty).
Во вторых, забыть написать .value() возможно также, как и написать unsafe {} в Rust(т. е. это просто из серии "страшилки на ночь").
Можно забыть написать unsafe в Rust. Полученный код не скомпилируется. А вот если забыть написать .value() в C++, то полученный код скомпилируется (но на отдельных данных будет вызывать неопределенное поведение).
С одним числом - скучная задача - достаточно, например, посчитать сумму mod 51. Более интересный вопрос, что делать, если забрали два кубика, а не один.
Генные векторы это способ доставки чужого ДНК в ваше ДНК, т.е внедрение чужого гена в ваш геном.Так получается, что это никакая не вакцина.
Внедрение чужого генома в ваши клетки, а не в ваше ДНК.
Короновирус (как и остальные вирусы) тоже доставляет свой генетический код (у гриппа и короновируса - РНК, но есть и ДНК-содержащие вирусы) в клетки. И даже есть вирусы (ретровирусы), которые действительно встраиваются в ДНК.
Но что короновирус, что РНК-вакцины в ДНК клетки не встраивается (это проверяли).
При наличии обработчика выше передавать наверх result, а не паниковать, вроде ничем не плохо.
? позволяет привести тип ошибки к тому типу, который нужен функции по сигнатуре. Минимальный пример.
В не-ядерном коде можно сделать очень немного, если попытка push закончилась ошибкой нехватки памяти. Я бы ожидал, что в тех местах, где разумные действия есть сейчас (или появятся в будущем) - интерфейсы поменяются, будут как раз result.
У них есть другие проблемы. Например, как написать unsafe rust так, чтобы не было проблем с алиасингом.
Это, очевидно, баг в подсчёте ограничений в where clousre. Вот иллюстрация той же самой проблемы, без использования 'static: https://github.com/rust-lang/rust/issues/84591.
Если бы не two compelemnt был бы багом, то все оптимизации компилятора, которые завязаны на не-переполнение signed int, были бы багом. Вообще, "переполнение знаковых - это undefined behaviour" - это фича языка, а не бага - оптимизировать код, в котором программист задумался и избежал можно лучше (и компиляторы это делают).
Другое дело, что на результат работы программиста это накладывает дополнительные ограничения.
Почему именно fakestatic, а не ещё один из примерно 70 unsound багов?
Вообще то throw (в виде panic!) он тоже вполне написать может. Если нужна паника - можно никакого unwrap не делать.
Зачем для raii (в растовском варианте) нужны обрабатываемые исключения? Зачем нужна какая-то обработка паники (типа выполнения деструкторов на raii) - понятно. Но зачем нужна абстракция ловли паники?
Утверждение "Компилятор гарантирует, что если stdlib написан корректно, то safe код, написанный поверх stdlib, не приведёт, например, к разименованию нулевого указателя." абсолютно прозрачно меняется, если поменять stdlib на другой источник unsafe кода.
"Компилятор гарантирует, что если весь unsafe код (включая код зависимостей) написан корректно, то safe программа не вызовет разыменование нулевого указателя".
У safe rust есть гарантии от компилятора. У unsafe rust - гарантий от компилятора гораздо меньше (и так же как в C# unsafe может сломать выполнение где-то дальше, а не в моменте вызова unsafe).
В тот момент, когда нужна арифметика указателей (а точнее разыменование указателя, полученного арифметическим действием) - нужен unsafe. Примерно потому что компилятор в compile time не может проверить (в общем случае), что в коде должно быть именно написанное вами
, а не pl <= epl (вместо !=) во второй строке. Формально проверять границы в compile time на голых указателях невозможно (и в итоге появляютсе оверхеды, если это делать каждый раз).
Там последовательность нескольких функций с дерективой inline. Компилятор отдельно очень попросили всё свернуть вместе.
Во-первых, в какой момент быстродействие стало частью семантики языка? gcc разных версий (или gcc и clang) компилирует один и тот же код (написанный на C++11, например) в разные программы с разной производительностью. Значит ли это, что между релизами gcc у языка поменялась семантика?
Во-вторых, оптимизации llvm, полезные для раста, разработчики раста тоже делают (и их вклад в llvm ненулевой).
Разрешите, начну с конца.
Вот это очевидно не правда. Верно, что для любого кода на 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 так же работает совсем без оверхеда. Вообще довольно большАя часть абстракций, представленных стандартной библиотекой, содержит небольшой оверхед, если содержат.
ПО пишут люди. Даже в стандартной библиотеке и компиляторе любого из современных языков регулярно находят ошибки. Поэтому писать "если совсем нет ошибок, то ..." - это слишком сильное утверждение.
Для stdlib (и любой другой библиотеки использующей unsafe) сам компилятор корректность гарантировать не может. Так же как с C# unsafe - можно выстрелить себе в ногу в unsafe блоке, а упадёт само приложение где-то дальше.
Но никто не мешает написать быструю библиотеку для чего-то своего поверх stdlib. Как раз в силу того, что абстракции дешёвые во время исполнения (и компиляции) она будет достаточно близка к low-level.
Да, если нужна работа с железом (на уровне регистров, например), придётся писать какой-то unsafe код (или пользоваться другой библиотекой, которая предоставляет безопасную абстракцию поверх этих регистром).
В Unsafe rust можно сделать double free. Вроде, никто этого не отрицал.
Да, именно так. Любые програмные примитивы - это машкод в другой нотации (или, наоборот, любые програмные примитивы, включая C++ - это не машкод, компиляция с оптимизацией может много всего переставить или даже векторизовать). В safe rust swap - это програмный примитив с примерно нулевой дополнительной стоимостью (при компиляции). Что и записывалось в преимущества (и откуда началась эта ветка):
Гарантии safe rust выше, чем гарантии C++ (даже с линтером). Например вот здесь обсуждалось, что даже мощные линтеры к С++ не отвергают некорректный код, который отвергает раст (да, Раст иногда отвергает корректный код, корректность которого он не может доказать. По теореме Райса, если ты отвергаешь весь некорректный код, то обязан отвергать и какой-то корректный).
Гарантии unsafe rust действительно близки к гарантиям C++ (чуть получше с арифметикой, зато программисту нужно ещё поддерживать инварианты алиасинга на границе unsafe).
Компилятор гарантирует, что если stdlib написан корректно, то safe код, написанный поверх stdlib, не приведёт, например, к разименованию нулевого указателя.
Да, предположение о корректности является довольно сильным. Но если стандартная библиотека содержит серьёзные ошибки, то на любом языке программирования у Вас проблемы.
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++. Зато результат получается более сильным - если получилось сделать безопасную абстракцию (и реализовать её без ошибок), то можно писать безопасный код, у которого много гарантий проверяется компилятором.
Вопрос уровня гарантий. Для соблюдения гарантий 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>)>, если второй не бывает представлен без первого (для массивов алгебраичиских типов уже не хватает, да).
Не просто безопасные, а безопасные и дешевые. Раст даёт довольно высокий уровень гарантий (в рамках 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>, например, можно случайно оставить звёздочку. А в расте нельзя - не скомпилируется.
Как правильно безопасно реализовывать swap без дополнительной памяти и внешней гарантии на то, что объекты не пересекаются по памяти?
Вопрос в скорости абстракции. Написанная на питоне программа будет (почти всегда) работать существенно медленнее, чем программа, написанная на C++. Rust-абстракции в этом смысле гораздо более быстрые.
Если вы проверили это уже где-то раньше - поменяйте тип с Option<T> на T, после этого проверять не нужно. Если данные приходят в функцию в виде Option<T>, то проверять нужно (даже если сегодня мы знаем, что там всегда что-то есть, то завтра кто-то может попробовать переиспользовать эту функцию по её сигнатуре и передаст туда Empty).
Можно забыть написать unsafe в Rust. Полученный код не скомпилируется. А вот если забыть написать .value() в C++, то полученный код скомпилируется (но на отдельных данных будет вызывать неопределенное поведение).
С одним числом - скучная задача - достаточно, например, посчитать сумму mod 51.
Более интересный вопрос, что делать, если забрали два кубика, а не один.
Беглый поиск по github нашёл cargo-mirror, написанный на питоне.
Но для конкретного пакета (или списка пакетов) достаточно cargo fetch: https://doc.rust-lang.org/cargo/commands/cargo-fetch.html
Например, деректива
#![forbid(unsafe_code)]
запрещает писать unsafe в крейте.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
Внедрение чужого генома в ваши клетки, а не в ваше ДНК.
Короновирус (как и остальные вирусы) тоже доставляет свой генетический код (у гриппа и короновируса - РНК, но есть и ДНК-содержащие вирусы) в клетки. И даже есть вирусы (ретровирусы), которые действительно встраиваются в ДНК.
Но что короновирус, что РНК-вакцины в ДНК клетки не встраивается (это проверяли).