Pull to refresh

Comments 36

  1. Для арифметики можно использовать wrapping-методы или даже тип-обёртку Wrapping.

  2. Для доступа к памяти можно использовать указатели, раз у нас уже всё равно unsafe и мы полагаемся на "обещания о разумности кода". Анализировать корректность явного доступа к памяти будет проще, чем assert, спрятанный в check_invariant.

Хорошо бы в Rust в принципе упростить возможность работать в пределах такого подмножества языка, которое не предполагает паник

можно просто взять С++ или С

оптимизатор Rust.


это называется LLVM )

Взять С++ и получить и получить UB в миллионе мест, это конечно то что надо для системной библиотеки.

А ничего что в расте разное поведение на релизе и на дебаге буквально в обсуждаемом месте (в релизе ничего не делают, а в дебаге кидают панику), то есть поведение наблюдаемое меняется. Это ж абсурд

Можно настроить одинаковое поведение

Вы никогда не слышали про debug assert? И ничто не мешает отключить его.

Дело в том, что Rust говорит, что переполнение знаковых чисел не является undefined behavior. А потом у вас разное поведение на дебаге и релизе для переполнения знаковых

Это непоследовательность

UB это значит что поведение не определенно. Если поведение управляется флагами компиляции и описано в спецификации, то это буквально по определению defined behavior. Вы можете и в релизе включать панику при переполнении, указав

[profile.release] overflow-checks = true

а где будут УБ, ллвм, технически ничего лучше не сгенерирует чем то что она умеет, а это значит в С++ всё зависит от подхода программирования

я уверен что можно так же свой слой трансляции написать, который как и раст делает импл к структрутре но с указателем, и так же обеспечить безопасность

LLVM это всё же бэкэнд, которые кушает промежуточный код, поэтому UB возникает на уровне компилятора. На с++ можно писать так, чтобы избегать UB, но это довольно сложно и требует довольно обширной машинерии - тесты, фаззинг, статические анализаторы, про покрытие думать. Ну и стандартная библиотека С++ абсолютно точно под это не заточена, поэтому придётся использовать, что-то сторонее и вероятно страдать от увеличения времени компиляции из-за тонны шаблонов. Собственно, как-от так и появляются библиотеки типа flux или beman. Остаётся гадать найдется ли достаточно библиотек, чтобы покрыть все требования к вашему проекту или придётся писать недостающее самому.

у меня по покрытию так выходит, плюс есть валгринд и асан, scan-build, clangd

Скрытый текст

ну там смотря как писать(так придётся тогда математику свою писать, всю библиотеку переписывать ради premature optimisation), если аккуратно, то можно что-то придумать, а у Раста какое преимущество, байткод Раст не сгенерирует такой какого нету - какой не поддерживает внутрянка подлинкованная к llvm

В расте банально проще писать тесты. Причем любых мастей - юниты, интеграцию, фаззинг/пропы. Открытых аналогов какого-нибудь kani для С++ и вовсе не встречал, хотя если не ошибаюсь существуют коммерческие решения и штуки типа TLA+ (например, доклад), но это уже сам по себе отдельный язык.
У Rust статический анализатор фактически часть компилятора. Для unsafe кода есть интерпретатор miri вместо asan/ubsan.

Ну в C++ хотя бы cout << "Hello, World!"; не паникует =)

Пример смешной, а ситуация страшная. По факту, неявные вызовы паники, вставляемые компилятором, это UB.

Панику не выдаст, он выдаст эксепшен если устройство не доступно или UB если вызов вне мейна, например в деструкторе когда std::cout уже разрушен. Конечно "это другое"(с)

он выдаст эксепшен

При дефолтных параметрах С++ не выкидывает тут исключение.

Но проблема не специфичная для Раста. Неявные вызовы везде неприятны (в других ЯП, например) :

  • неявные эксепшны

  • неявные выделения памяти, которые могут ещё и обломиться

  • неявный вызов GC

  • неявный вызов деструктора не вовремя

Нужно иметь механизмы контроля. Непонятно, почему автора не устроило все же #[no_panic], лень писать?

поидее можно его проверить же

...
 std::ostream *stream;
...
 std::string_view output{t};
...
 if(file.stream){//cout у меня в одной структуре оба
  *file.stream << output << std::endl;
 }

у юнита std::ostream есть fail bool, exceptions, bad, eof итд

Когда раст уберет отовсюду unsafe кроме системных вызовов, тогда можно поговорить про его особенность. Пока раст тот же ub, просто в гораздо меньшем количестве, и оно не уменьшается.

в гораздо меньшем количестве

На фоне сишечек — уже огромное достижение

так на си можно тоже дописать слой трансляции(даже не побоюсь этого) по управлению указателями

тоесть

int a=1;

int *b = &a;

всё вокруг этого да?

по этой причине С быстрее Раста скорее всего

а если не так делать, а так то вроде медлнее но я не замерял

https://godbolt.org/z/aeznsKojM

Вы о чём вообще?

Код по ссылке легко валится написанием какого-нибудь банального *(SafePtr((int*)666)) = 666;

На Rust вы не сможете так сделать, не используя unsafe — тем Rust и прекрасен

А что принципиально изменилось? Просто убираем = 666 и получаем free(): invalid pointer

https://godbolt.org/z/sP9oEYnbM

я на голых структурах и голых указателях пишу и не пишу такие конструкции как вы показали или как в той статье на английском языке

И это всё не имеет абсолютно никакого смысла, пока C и C++ никак не защищают от случайного написания кода подобного *((size_t*)0) = 0; в обход всех этих ваших SafePtr

А еще на Rust нельзя написать L1 список без unsafe...

Ну да, "А в этой вашей Америке, вообще, негров линчуют!" (С).

Я (по работе) пишу на Скакалке (Scala), вообще говоря, функциональном языке. Но под капотом у некоторых функций, порой, сплошная императивщина. Но это никому не мешает, если это хорошо закопано, оттестировано и не порождает сайд-эффектов!

А какая связь между unsafe и ub?

В Rust теоретически UB может быть только внутри unsafe. На практике добиться UB в safe-code можно только очень странными эзотерическими способами.

Каша их топора какая-то получилась. Автор избавился от panic добавив assert_unchecked, что определённо хуже, т. к. последнее в случае ошибки в коде тихо завалит или попортит программу. Зачем спрашивается? Чтобы выкинуть 300 кб кода? Так можно было бы вместо этого тупо отключить размотку стека и добавить свой минималистичный обработчик panic, а не заниматься казуистикой.

Чтобы выкинуть 300 кб кода?

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

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

Тут вспоминаются языки в которых существует Trap-handler

Автор избавился от panic добавив assert_unchecked, что определённо хуже, т. к. последнее в случае ошибки в коде тихо завалит или попортит программу.

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

Структуру S мы можем создать только одним способом
А потом добавляем метод, который иногда нарушает инвариант...

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

Но данная оптимизация в принципе редко применима на практике, это правда. В прикладных программах, очевидно, так никто делать не станет: код становится слишком сложнее. Цена за выигрыш слишком высока, всем плевать на лишние 300кб.

Может быть и так, тоже вариант. Но, как известно, "С дуру можно и хрен сломать, хоть он и без костей!" (С).

Автор выбрал компромисс. Компромисс его устроил и он поделился с сообществом. Вай нот? По мне -- отличная идея!

По размеру кода определить отсутствие паник?

Или размер кода именно нужен?

#[no_panic] 

должна при компиляции отловить наличие паник

Я пытался её использовать, но оказалось, что у неё слишком много ложноположительных срабатываний

Sign up to leave a comment.

Articles