Обновить
Комментарии 27

"Ни разу не возникало ощущения, что Rust каким-то образом сдерживает меня или что я жертвую производительностью или функциями, потому что использую Rust, а не C" — ну да, а те же приседания с get_unchecked в unsafe блоках — это не оно.

И где же в использовании get_unchecked жертвование производительностью или возможностями (в оригинале capabilities)? Если бы было про краткость/лаконичность ещё можно было бы согласиться, но в текущем варианте, нет, не оно.

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

Ну есть выбор — медленнее, но с проверками или быстрее, но без проверок. Вот если бы опции "быстрее, но без проверок" в расте не было, но он бы действительно "каким-то образом сдерживал".

А еще есть выбор «с отключаемыми проверками» — это когда не нужно жертвовать ничем, т. к. можно везде вставлять медленные проверки, которые потом автоматически будут убраны. Наверняка такое можно сделать в Расте, но стандартных средств автор не нашел.

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


У автора всё-таки особенный случай.

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

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


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

Давайте определимся, что значит "отключаем проверки"? Получаем UB, если вдруг что-то пошло не так? В таком случае, проверки очень даже нужны.


На примере get_unchecked из статьи: в идеальном случае мы можем передать компилятору знание о том, что проверка не нужна (например, если у нас есть итератор) — это будет ещё и безопасно. В некоторых случаях мы обладаем знанием, что операция безопасна, а компилятор нет. В этом случае используем "костыли" (get_unchecked) и обкладываем код тестами. Делать так везде скорее вредно: утверждения, которые мы в уме держим (тут массив точно больше десяти элементов!) могут быть ошибочны.


У автора весь смысл проекта сделать как можно быстрее и меньше. В других проектах обычно коректность и отсутствие порчи данных важны.

Отключаем — значит используем что-то вроде этого:
#ifdef NDEBUG
#define assert(expr)
#else
#define assert(expr) if (!(expr)) { <some logging and debug break>; }
#endif // #ifdef NDEBUG

Для релиза вместо no-op'а можно еще хинты для оптимизатора поставить, вроде как уже ввели стандартизированный способ.
Да, в релизе это может добавить UB, только вот на стадии тестирования подавляющая их часть будет отловлена. Если же по какой-то причине надо отдать в продакшен какую-то нетестированную часть проекта — просто переопределите макрос так, что бы в релизе было то же, что в дебаге, и получайте логи об ошибках.
В общем то я уверен, что аналогичные дела можно реализовать в Rust, но, насколько я понимаю, стандартные паники, на которых написана стандартная библиотека и, думаю, огромная часть нестандартных, таки не подлежат отключению (как минимум автор не нашел способа это сделать).
Да, в релизе это может добавить UB, только вот на стадии тестирования подавляющая их часть будет отловлена.

Не будет.

О, ну раз Вы сказали «не будет» — да, значит и впрямь не будет. /s

Так я о том и говорю, что с таким подходом можно или отключить все проверки сразу или включить. Можно, конечно, определять дефайны для отдельных частей проекта, но с таким сталкивался редко. Ну а дальше всё упирается в устраивает ли нас утверждение "(думаем, что) большая часть UB отловлена". Могу только повторить, что предпочитаю уменьшать объём кода надёжность которого гарантируется "вручную".


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

Меня больше смутило использование glsl… Всегда казалось, что тру — это только побайтовая запись в видеопамять.
OpenGL давно уже используют, также как и шейдеры. Вся работа с фреймбуфером вынесена в шейдеры.

Я считываю тон вашего комментария, но не улавливаю его смысл. Не могли бы вы раскрыть смысл?

Это-то понятно. А можно объяснение для не sapienti? Что, для демосцены обязательно нужно использовать ассемблер или один из самых старых и убогих языков программирования?

Боюсь, что подобную демку можно было написать вообще одним шейдером. Ну а цикл вызова любым дефолтным языком с передачей времени и еще каких-либо опорных данных в шейдер.
Отлично получилось
Меня очень возмутила сборка раста из коробки на 180 кб, сократить ее значительно оказалось возможно, но до 4 кб дойти выглядит подвигом.
Хотя понятно, что стандартная библиотека.

красивые кроссплатформенные паники и форматирование дорого стоят в плане объема. Плюс весь тот обвязочный код который намазывается компилятором без вашего ведома там же.

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

Дело в том, что подобный цикл


for x in 0..10 {
    // do code
}

Развернется в следующий код:


match IntoIterator::into_iter(0..10) {
    mut iter => loop {
        let next;
        match iter.next() {
            Some(val) => next = val,
            None => break,
        };
        let x = next;
        let () = {
            // do code
        };
    },
}
Minor, но нужно дополнительно mut
let mut x = 0;
loop {
    // do code
    x += 1;
    if x == 10 {
        break;
    }
}
поясните, а чем это демо принципиально отличается от демо mars 1993года объёмом 3кБ?
ps: то есть это не совсем демо — это игра, по Марсу можно летать =)

cloud.mail.ru/public/4Taj/2zMm29QK9

Принципиально — ничем. Хотя это зависит от уровня вашей принципиальности )

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