Перенос Quake 3 на Rust

Автор оригинала: Andrei Homescu, Stephen Crane, Miguel Saldivar
  • Перевод

Наша команда Immunant любит Rust и активно работает над C2Rust — фреймворком миграции, берущим на себя всю рутину миграции на Rust. Мы стремимся автоматически вносить в преобразованный код на Rust улучшения безопасности и помогать программисту делать это самому, когда не справляется фреймворк. Однако в первую очередь нам нужно создать надёжный транслятор, позволяющий пользователям приступить к работе с Rust. Тестирование на мелких CLI-программах потихоньку устаревает, поэтому мы решили перенести на Rust игру Quake 3. Спустя пару дней мы, скорее всего, стали первыми, кому удалось сыграть в Quake3 на Rust!

Подготовка: исходники Quake 3


Изучив исходный код оригинального Quake 3 и различных форков, мы остановились на ioquake3. Это созданный сообществом форк Quake 3, который до сих пор поддерживается и собирается на современных платформах.

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

$ make release

При сборке ioquake3 создаётся несколько библиотек и исполняемых файлов:

$ tree --prune -I missionpack -P "*.so|*x86_64"
.
└── build
    └── debug-linux-x86_64
        ├── baseq3
        │   ├── cgamex86_64.so          # client
        │   ├── qagamex86_64.so         # game server
        │   └── uix86_64.so             # ui
        ├── ioq3ded.x86_64              # dedicated server binary
        ├── ioquake3.x86_64             # main binary
        ├── renderer_opengl1_x86_64.so  # opengl1 renderer
        └── renderer_opengl2_x86_64.so  # opengl2 renderer

Среди этих библиотек, библиотеки UI, клиента и сервера можно собрать или как сборку Quake VM, или как нативные библиотеки общего пользования X86. В своём проекте мы решили использовать нативные версии. Транслирование VM на Rust и использование QVM-версий были бы значительно проще, но мы хотели тщательно протестировать C2Rust.

В своём проекте переноса мы сосредоточились на UI, игре, клиенте, OpenGL1-рендерере и основном исполняемом файле. Можно было бы транслировать и OpenGL2-рендерер, но мы решили пропустить это, потому что в нём используется значительный объём файлов шейдеров .glsl, которые система сборки встраивает как строковые литералы в исходном коде на C. После выполнения транспиляции мы добавим поддержку скриптов сборки для встраивания кода GLSL в строки Rust, но пока нет хорошего автоматизированного способа транспиляции этих автоматически сгенерированных временных файлов. Поэтому вместо этого мы просто транслировали библиотеку OpenGL1-рендерера и принудительно заставили игру использовать его вместо рендерера по умолчанию. Кроме того, мы решили пропустить выделенный сервер и упакованные файлы миссий, потому что их несложно будет перенести и они необязательны для нашей демонстрации.

Транспилируем Quake 3


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

Так как C2Rust создаёт файлы сборки Cargo, каждый двоичный файл требует собственного Rust crate с соответствующим файлом Cargo.toml.

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

Однако мы быстро столкнулись с одним ограничением, вызванным тем, как C2Rust перехватывает нативный процесс сборки: C2Rust получает на входе файл базы данных компиляции, который содержит список команд компиляции, выполняемых во время сборки. Однако эта база данных содержит только команды компиляции без вызовов компоновщика. Большинство инструментов, создающих эту базу данных, имеют это намеренное ограничение, например, cmake с CMAKE_EXPORT_COMPILE_COMMANDS, bear и compiledb. По нашему опыту, единственным инструментом, включающим команды компоновки, является build-logger, созданный CodeChecker, который мы не использовали, потому что узнали о нём только после написания собственных обёрток (о них рассказывается ниже). Это означало, что для транспиляции программы на C с несколькими двоичными файлами мы не могли использовать файл compile_commands.json, созданный любым из распространённых инструментов.

Поэтому мы написали собственные скрипты обёрток компилятора и компоновщика, которые дампят все вызовы компилятора и компоновщика в базу данных, а затем преобразуют его в расширенный compile_commands.json. Вместо обычной сборки, использующей команду наподобие:

$ make release

мы добавили обёртки для перехвата сборки при помощи:

$ make release CC=/path/to/C2Rust/scripts/cc-wrappers/cc

Обёртки создают каталог из нескольких файлов JSON, по одному на вызов. Второй скрипт собирает всех их в один новый файл compile_commands.json, который содержит команды и компиляции, и компоновки. Затем мы расширили C2Rust, чтобы он считывал команды компоновки из базы данных и создавал отдельный crate на каждый скомпонованный двоичный файл. Кроме того, C2Rust теперь ещё и считывает библиотечные зависимости для каждого двоичного файла и автоматически добавляет их в файл build.rs соответствующего crate.

Для повышения удобства все двоичные файлы можно собрать за раз, расположив их внутри workspace. C2Rust создаёт файл workspace Cargo.toml верхнего уровня, поэтому мы можем собрать проект единственной командой cargo build в каталоге quake3-rs:

$ tree -L 1
.
├── Cargo.lock
├── Cargo.toml
├── cgamex86_64
├── ioquake3
├── qagamex86_64
├── renderer_opengl1_x86_64
├── rust-toolchain
└── uix86_64

$ cargo build --release

Устраняем шероховатости


Когда мы впервые попробовали собрать транслированный код, то столкнулись с парой проблем с исходниками Quake 3: возникли граничные случаи, которые C2Rust не мог обработать (ни корректно, ни вообще хоть как-нибудь).

Указатели на массивы


В нескольких местах оригинального исходного кода содержатся выражения, указывающие на следующий после последнего элемент массива. Вот упрощённый пример кода на C:

int array[1024];
int *p;

// ...

if (p >= &array[1024]) {
   // error...
}

Стандарт C (см., например, C11, Section 6.5.6) позволяет указателям на элемент выходить за конец массива. Однако Rust запрещает это, даже если мы только берём адрес элемента. Мы нашли примеры такого паттерна в функции AAS_TraceClientBBox.

Компилятор Rust также сигнализировал о подобном, но на самом деле содержащем ошибку примере в G_TryPushingEntity, где условная инструкция имеет вид >, а не >=. Вышедший за границы указатель затем разыменуется после условной конструкции, что является багом безопасности памяти.

Чтобы избежать этой проблемы в будущем, мы исправили транспилятор C2Rust так, чтобы он использовал арифметику указателей для вычисления адреса элемента массива, а не применял операцию индексирования массива. Благодаря этому исправлению код, использующий подобный паттерн «адрес элемента за концом массива», теперь корректно транслируется и выполняется без модификаций.

Элементы массивов переменной длины


Мы запустили игру, чтобы всё протестировать, и сразу же получили panic от Rust:

thread 'main' panicked at 'index out of bounds: the len is 4 but the index is 4', quake3-client/src/cm_polylib.rs:973:17

Взглянув на cm_polylib.c, мы заметили, что он разыменовывает поле p в следующей структуре:

typedef struct
{
	int		numpoints;
	vec3_t	p[4];		// variable sized
} winding_t;

Поле p в структуре — это неподдерживаемая до стандарта C99 версия элемента массива переменной длины (flexible array member), который тем не менее принимается gcc. C2Rust распознаёт элементы массивов переменной длины с синтаксисом C99 (vec3_t p[]) и реализует простую эвристику, чтобы также определить версии этого паттерна до C99 (массивы размером 0 и 1 в конце структур; также мы нашли несколько таких примеров в исходном коде ioquake3).

Изменение представленной выше структуры на синтаксис C99 устранило panic:

typedef struct
{
	int		numpoints;
	vec3_t	p[];		// variable sized
} winding_t;

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

Tied Operands во встроенном ассемблерном коде


Ещё одним источником сбоев был этот встроенный в C ассемблерный код из системного заголовка /usr/include/bits/select.h:

# define __FD_ZERO(fdsp)                                            \
  do {                                                              \
    int __d0, __d1;                                                 \
    __asm__ __volatile__ ("cld; rep; " __FD_ZERO_STOS               \
                          : "=c" (__d0), "=D" (__d1)                \
                          : "a" (0), "0" (sizeof (fd_set)           \
                                          / sizeof (__fd_mask)),    \
                            "1" (&__FDS_BITS (fdsp)[0])             \
                          : "memory");                              \
  } while (0)

определяющий внутреннюю версию макроса __FD_ZERO. Это определение вызывает редкий граничный случай встроенного ассемблерного кода gcc: tied operands ввода-вывода с разными размерами. Оператор вывода "=D" (__d1) привязывает регистр edi к переменной __d1 как 32-битное значение, а "1" (&__FDS_BITS (fdsp)[0]) привязывает тот же регистр к адресу fdsp->fds_bits как 64-битный указатель. gcc и clang устраняют это несовпадение. используя 64-битный регистр rdi и усекая его значение перед присвоением значения __d1, а Rust по умолчанию использует семантику LLVM, в которой такой случай остаётся неопределённым. В отладочных сборках (не в релизных, которые вели себя хорошо) мы увидели, что оба операнда можно присвоить регистру edi, из-за чего указатель усекается до 32 бит перед встроенным ассемблерным кодом, что вызывает сбои.

Так как rustc передаёт встроенный ассемблерный код Rust в LLVM с очень небольшими изменениями, мы решили исправить этот конкретный случай в C2Rust. Мы реализовали новый crate c2rust-asm-casts, устраняющий данную проблему благодаря системе типов Rust, использующей trait и вспомогательные функции, автоматически расширяющие и усекающие значения tied operands до внутреннего размера, который достаточно велик, чтобы хранить оба операнда. Представленный выше код корректно транспилируется в следующее:

let mut __d0: c_int = 0;
let mut __d1: c_int = 0;
// Reference to the output value of the first operand
let fresh5 = &mut __d0;
// The internal storage for the first tied operand
let fresh6;
// Reference to the output value of the second operand
let fresh7 = &mut __d1;
// The internal storage for the second tied operand
let fresh8;
// Input value of the first operand
let fresh9 = (::std::mem::size_of::<fd_set>() as c_ulong).wrapping_div(::std::mem::size_of::<__fd_mask>() as c_ulong);
// Input value of the second operand
let fresh10 = &mut *fdset.__fds_bits.as_mut_ptr().offset(0) as *mut __fd_mask;
asm!("cld; rep; stosq"
     : "={cx}" (fresh6), "={di}" (fresh8)
     : "{ax}" (0),
       // Cast the input operands into the internal storage type
       // with optional zero- or sign-extension
       "0" (AsmCast::cast_in(fresh5, fresh9)),
       "1" (AsmCast::cast_in(fresh7, fresh10))
     : "memory"
     : "volatile");
// Cast the operands out (types are inferred) with truncation
AsmCast::cast_out(fresh5, fresh9, fresh6);
AsmCast::cast_out(fresh7, fresh10, fresh8);

Стоит заметить, что этот код не требует никаких типов для входных и выходных значений в конструкции ассемблерного кода, при разрешении конфликтов типов полагаясь вместо них на вывод типов Rust (в основном типов fresh6 и fresh8).

Выровненные глобальные переменные


Последним источником сбоев была следующая глобальная переменная, хранящая константу SSE:

static unsigned char ssemask[16] __attribute__((aligned(16))) =
{
	"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00"
};

В настоящее время Rust поддерживает атрибут выравнивания для структурных типов, но не для глобальных переменных, т.е. элементов static. Мы рассматривали способы решения этой проблемы в общем случае или в Rust, или в C2Rust, но в ioquake3 пока решили устранить её вручную коротким файлом patch. Этот файл patch заменяет эквивалент Rust ssemask следующим:

#[repr(C, align(16))]
struct SseMask([u8; 16]);
static mut ssemask: SseMask = SseMask([
    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
]);

Запуск quake3-rs


При запуске cargo build --release создаются двоичные файлы, но они создаются под target/release со структурой каталогов, который двоичный файл ioquake3 не распознаёт. Мы написали скрипт, создающий символьные ссылки в текущем каталоге для воссоздания правильной структуры каталогов (в том числе ссылки на файлы .pk3, содержащие ресурсы игры):

$ /path/to/make_quake3_rs_links.sh /path/to/quake3-rs/target/release /path/to/paks

Путь /path/to/paks должен указывать на каталог, содержащий файлы .pk3.

Теперь давайте запустим игру! Нам нужно передать +set vm_game 0 и т.п., поэтому мы загружаем эти модули как библиотеки общего пользования Rust, а не как сборку QVM, а также cl_renderer для использования OpenGL1-рендерера.

$ ./ioquake3 +set sv_pure 0 +set vm_game 0 +set vm_cgame 0 +set vm_ui 0 +set cl_renderer "opengl1"

И…


Мы запустили Quake3 на Rust!


Вот видео того, как мы транспилируем Quake 3, загружаем игру и немного в неё играем:


Можете изучить транспилированные исходники в ветке transpiled нашего репозитория. Также там есть ветка refactored, содержащая те же исходники с несколькими предварительно применёнными командами рефакторинга.

Как выполнить транспиляцию


Если вы хотите попробовать транспилировать Quake 3 и запустить его самостоятельно, то учтите, что вам потребуются собственные игровые ресурсы Quake 3 или ресурсы демо из Интернета. Также нужно будет установить C2Rust (на момент написания требуемая nightly-версия — это nightly-2019-12-05, но мы рекомендуем заглянуть в репозиторий C2Rust или в crates.io, чтобы найти самую новую версию):

$ cargo +nightly-2019-12-05 install c2rust

и копии наших репозиториев C2Rust и ioquake3:

$ git clone <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="dcbbb5a89cbbb5a8b4a9bef2bfb3b1">[email protected]</a>:immunant/c2rust.git
$ git clone <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="dcbbb5a89cbbb5a8b4a9bef2bfb3b1">[email protected]</a>:immunant/ioq3.git

В качестве альтернативы установке c2rust с помощью приведённой выше команды можно собрать C2Rust вручную при помощи cargo build --release. В любом случае репозиторий C2Rust всё равно понадобится, потому что он содержит скрипты обёрток компилятора, требуемые для транспиляции ioquake3.

Мы выложили скрипт, автоматически транспилирующий код на C и применяющий патч ssemask. Чтобы воспользоваться им, запустите следующую команду из верхнего уровня репозитория ioq3:

$ ./transpile.sh </path/to/C2Rust repository> </path/to/c2rust binary>

Эта команда должна создать подкаталог quake3-rs, содержащий код на Rust, для которого можно затем выполнить команду cargo build --release и оставшиеся шаги, описанные выше.
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 76

    –1

    До "собирается на современных платформах" всё же ещё далеко, пока что этот форк работает только под линукс

      +1

      ioquake3 должен работать на всех платформах, согласно README вот тут: https://github.com/ioquake/ioq3. Совершенно точно он работает под macOS.

        +4

        Исходный ioquake — вполне собирается и под windows. Но сравнивая репозитории очевидно, что winows-specific вещи исключены

          +1
          В исходном коде платформо-специфичные вещи исключили, потому что форк базируется на SDL2. Этот порт точно работал под Windows ещё пару лет назад — сам качал тестовые билды с сайта форка, чтобы никого не обделить на lan party в офисе ). Сейчас раздел пустой из-за взлома сборочной инфраструктуры, поэтому придётся собирать самому.
      +6

      Время сборки исходников компилятором сильно отличается?

        +4
        rust делает билд за 30 сек, судя по видео. А транспиляцию за 1:40 сек примерно.
        Осталось понять сколько делает все компилятор на С на такой же машине. Мне тоже интересно сравнить.
        +14
        Объясните мне пожалуйста, а зачем это всё?
          +17
          Исследовательский проект, можно понять много всякого, пока он делается. Так же спрашивали «на кой черт пыжиться kernel CLang-ом собирать, есть же GCC». Узнать что-то новое о наших инструментах)

          Ну и возможно кому-то на практике пригодится тулза которая конвертит исходники на C, например, какой-нибудь видео encoder, скажем, AV1 ;)
            +6

            Что Сишный, что ржавый вариант на 60% состоят из ассемблерных вставок под платформенные SSE. Это скорее что-то из разряда "как не переписать все на Rust". Есть какая-то легаси либа в проде, которая вроде и работает, но код ужасно пахнет, тестов толком не было и допилить что-то к ней надо. Скармливаем в C2Rust, допиливаем напильником и запиливаем недостающие фичи и используем как drop-in решение.

              +10
              Такое как раз лучше и не трогать или уж переписывать как надо. Смысла в конвертации около нуля и +N новых багов.
                +3

                Я имел ввиду ситуацию, когда к этому нужно новый функционал. Или есть какие-то странные плавующие SEGFAULT. Или есть завязки на другие либы, которые уже работают, но не имеют порта (типа SDL какой-нибудь для геймдева).
                А вообще было бы неплохо если б кто-нибудь взялся перевести пару постов по теме (раз, два). В первой статье конвертация помогла избавиться от древненего незадокументированного косяка, как пример. Где-то тут же на хабре была статья про сферический анализатор текстов в типографии, который надо допиливать и в Си-лэнде оно оставляет много проблем как с IO так и с доступом к памяти, но это уже про полноценный RIIR, не про конвертацию.

                  +2

                  А что, конвертация в Rust настолько умная, что поможет с плавающими SEGFAULT в С?

                  • НЛО прилетело и опубликовало эту надпись здесь
                      0

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


                      Оригинальная заметка со второй статьи
                      As an aside, this test was originally written to nest things three layers deep (e.g. top_level.vm includes nested.vm which includes really_nested.vm) to make sure it handles more than one level of %include, but no matter how it was written the test kept segfaulting.
                      Then I tried running the original C tvmi binary…
                      $ cd vendor/tinyvm/
                      $ cat top_level.vm
                      %include nested
                      $ cat nested.vm
                      %include really_nested
                      $ cat really_nested.vm
                      Hello World
                      $ ./bin/tvmi top_level.vm
                      [1]    10607 segmentation fault (core dumped)  ./bin/tvmi top_level.vm


                      Turns out the original tinyvm will crash for some reason when you have multiple layers of includes
                        0

                        какой-то детский сад получается

                  +1
                  Что? Это та сотня строк асма, которая компилируется лишь в случае целевой платформы x86?
                    +1

                    https://github.com/xiph/rav1e
                    Assembly 57.2%
                    Rust 42.5%
                    Other 0.3%
                    sloc на репу не травил, поэтому сколько сотен строк asm там не знаю. в сишной имплиментация похожая история.

                      0

                      Там еще часть раст кода https://github.com/xiph/rav1e/tree/master/src/asm/x86 — unsafe функции из интринзиков.

                        0
                        А руками исходник-то не смотрели (ох, люблю я русский язык)?
                        Или, может, там под assebmly другое подразумевается?
                        Вообще-то Кармак не любил ассемблер начиная с первого Doom, даже там его самую чуточку.
                          +1

                          Ну, так кармак игры писал, а не выжимал из железа каждый бит. Производительность кода — скорее побочный эффект, необходимый для комфортной игры, нежели цель. У кодеков же бенчмаркинг едва ли не такты считает ради оптимальнейшей утилизации ресурсов железа, чтоб обработка чуть ли не в скорость шины данных упиралась, энергоэффективность тут же рядом.
                          Да, исходники смотрел, но по большей части уделял ржавой части и arm neon. Не слишком детально, конечно.
                          У сишного dav1d cхожие автометрики. Нюанс в том, что ассемблерный код не слишком компактный и десяток строк высокоуровневого языка вполне может вылиться в пару сотен строк на asm, а метрики gitlab и github почти наверняка ориентируются именно на sloc ничего удивительного, что "10 строк" asm не очень десять, а половина значимых возвратов каретки в репе

                    +2
                    Интересно, пришло ли подтверждение того, что Rust действительно легче в написании и поддержке, безопаснее и т.д.?
                      +9
                      Код покрыт ансейфами абсолютно весь:
                      $ find ~/prog/ioq3 -name "*.rs" | xargs cat | grep unsafe | wc -l
                      9241


                      Ну это так, чтобы оценить масштаб проблемы
                      $ find ~/prog/ioq3 -name "*.rs" | xargs cat | grep fn | wc -l
                      9740

                        +2

                        Это игрушки из серии C++ в Java получаются — никакого коммерческого смысла подробные конвертации не имеют.
                        Что, в принципе, и ожидалось.

                          +3

                          <offtop>


                          Код покрыт ансейфами абсолютно весь

                          Пропел на мотив "Острова невезения". Захотелось записать переделку. Хабр, ты что творишь...

                    +2

                    прочитал


                    C2Rust — фреймворком миграции, берущим на себя всю рутину миграции на Rust.

                    на самом деле это очень круто!


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


                    С другой стороны Раст призван вывести Си программистов из зоны комфорта чтоб те перестали кодить устаревшими патернами и перешли на новую логику, и получили прирост производительности...

                      +2
                      Пока есть много спецов, которые умеют справлятся с C и C++ и нужны как для новых, так и для существующих проектов, пока индустрия не начнёт осознавать что проблемы с этими языками куда дороже чем поиск/обучение специалистов на раст, то ожидать перехода не стоит. Тем более что индустрия C/C++ куда более консервативна и фундаментальна, чем прикладное ПО или веб, где сейчас ты пишешь на React JS, а завтра уже пишешь на Dart или ClojureScript, так как это хайпово, а бизнес требует выпускать всё больше новых и модных продуктов в короткие сроки. В системной же нише решения создаются на годы вперёд и держат над собой все эти прикладные вещи, и переход на новый язык требует оценки рисков, а сама технология и языки — долгой, многолетней «обкатки», после которой он получит признание. И то, в лучшем случае только для новых проектов, так как переписывать старые редко имеет смысл.
                        –1
                        А ещё MS задумал свой раст с преферансом ну и так далее. Вот они смогут толкнуть его(свой вариант).
                          +1

                          +1 стандарт? Не, спасибо, нам такого не надо — раста достаточно.

                            –2
                            у раста специфический синтаксис.
                            Вот если бы сделали удобный C-шный синтаксис, другое дело
                              –1
                              как раз было бы счастье, если бы MS сделал нормальный тулинг и нормальный синтаксис, да и своим примером показал как они с сишечки переезжают
                                +6

                                Да что вам в этом синтаксисе не нравится-то всё...

                                  +6

                                  Синтаксис Раста кажется страшным только до тех пор, пока вы не начнете писать на нем программы )

                                +5
                                С их слов это не форк языка (надеюсь), но сам факт того, что индустрия начала интересоваться — хороший знак. Прошлой осенью ещё предлагали писать драйвера для линукс ядра. Хоть это и будет опциональная вещь, но она явно создаст первый крупный прецедент.
                                  0
                                  Проект верона… они ругали раст за медленную скорость разработки, вот-вот только асинки появились, а в шарпе они были когда. Просто Rust — это коллективный продукт, решения принимаются медленно но верно, а корпорация, конечно, может быстрее в десять раз сделать, лишь бы не испортила.
                                    0
                                    я надеюсь только на с лайк синтаксис, мне растовый не нравится.
                                      +2
                                      Сейчас немодно писать тип впереди переменной, все новые языки пишут через двоеточие после, а для функции через стрелку, как же можно идти против тренда :)
                                        +3
                                        «Да вы что, не знаете, что в однобортном сейчас уже никто не воюет?» ©
                                          +2

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

                                            0
                                            Длинный женерик указывать перед именем, да еще через пробел — это понятно, очень нечитабельно, особенно если перед именем функции, кому такое может понравиться, а про вывод типа не понял.
                                              +2
                                              Видимо, имеются в виду случаи типа такого, когда return type функции выводится из типов ее аргументов. Для подобных случаев в C++ тоже сейчас добавили стрелочный синтаксис.
                                                +1
                                                в С++14 такое есть не только для лямбд с их стрелочным синтаксисом, но и для обычных функций:
                                                template <typename T1, typename T2>
                                                auto add(T1 lhs, T2 rhs) { return lhs + rhs; }
                                                  +1
                                                  Ну это хорошо, конечно, если тело функции идет тут же, а если это предварительное объявление?

                                                  template <typename T1, typename T2>
                                                  decltype(lhs + rhs) add(T1 lhs, T2 rhs);
                                                  

                                                  не прокатит, lhs/rhs was not declared in this scope. А вот такое:

                                                  template <typename T1, typename T2>
                                                  auto add(T1 lhs, T2 rhs) -> decltype(lhs + rhs);
                                                  

                                                  прокатит без проблем.
                                                    0
                                                    вопрос в рамках самообразования, а почему такое нельзя?
                                                    template <typename T1, typename T2>
                                                    decltype(lhs + rhs) add(T1 lhs, T2 rhs);
                                                      +3
                                                      Ну, очевидный ответ — потому, что на момент парсинга конструкции с decltype про lhs и rhs еще ничего не известно, так как они объявлены позже. Менее очевиден ответ на вопрос, почему на такой случай не сделали какое-нибудь «исключение из правил» для парсера, этого я точно не знаю, не берусь ничего утверждать.
                                                        +2

                                                        если про lhs + rhs ничего не известно на момент парсинга decltype(lhs + rhs) — то это совершенно не проблема. Проблема возникает, если такие переменные объявлены выше по коду.


                                                        double lhs;
                                                        double rhs;
                                                        
                                                        decltype(lhs + rhs) add(int lhs, int rhs);

                                                        Какой тип результата по твоему должен быть у функции ??

                                                          +1

                                                          Ну да, такое тоже может быть.

                                                            –2
                                                            int конечно

                                                            сунь код в компилятор
                                                              0
                                                              попробовал, получилось, что double: godbolt.org/z/FTYdun
                                                                –1
                                                                мда уж, трапчик.

                                                                потому что неправильно написано. должно быть
                                                                auto add(int lhs, int rhs) -> decltype(lhs + rhs) ;
                                    +2

                                    Смысл переписать есть, денег нет, зачастую.

                                      +1

                                      Переписывание ради переписывание тоже смысла не имеет — только если есть какие-то значимые бенефиты.

                                  +9
                                  Посмотрел ioquake3/src/server/sv_init.rs, вспомнил выражение:
                                  Программист на Фортран может писать на Фортране на любом языке программирования.
                                    –12
                                    Мало им «окисления» Firefox, rsvg, теперь за ioQuake взялись, вместо того чтобы Crates.IO сделать offline-совместимым как PyPi, cpan.

                                    Нужно по возможности защитить весь Open-Source софт от этой заразы путем форка.
                                      +4
                                      Это что, есть порты Doom и Quake на дельфи. И все живы-здоровы, никто не умер.
                                        +2
                                        В случае с Delphi это были эксперименты по портированию. Игры с портированием на Rust заканчиваются тем что на него переключают основную ветку разработки, посмотрите на теже Firefox и rsvg, новые версии без Rust просто не собираются.
                                          0
                                          Какая основная ветка нафиг? IOQUake3 сам по себе форк.
                                            0

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

                                              –2
                                              Отличная новость! Если есть решение, значит проблема таки была.

                                              Нажать на кнопку «Нравится» не могу, заминусовали.

                                              Кто наставил минусов, убирайте, я был прав, указанная мной проблема с использованием Rust в оффлайне имела место!
                                          +1

                                          А где же фраза "го вместе защищать, я создал"?

                                            0
                                            Вместе не получится. Многим не до этого, много народа на винде сидит им плевать на опенсорс.

                                            Лично я играю в Urban Terror, который на ioquake3 крутится. Каждую неделю, каждый месяц, каждый год к ряду играю — это уже часть моей жизни.
                                          +8
                                          А главное — где сравнение pps (popugaev per second)?
                                            +4
                                            Результирующий код напомнил времена, когда HTML страницы делали в MS Word
                                              +5
                                              Если копнуть C2Rust чуть глубже, то можно обнаружить, что там есть инструменты для полуавтоматического переноса результирующего кода на идиоматичный раст. Но авторы решили не заморачиваться, а жаль.
                                                –1
                                                я не совсем понял… зачем? только ради теста?
                                                  0

                                                  А зачем может быть нужен указатель за пределами массива?

                                                    0

                                                    указатель в принципе может указывать на ЛЮБОЙ адрес в памяти. NULL — так-то это тоже адрес ЗА пределами массива. Так что если так рассуждать, то от указателей надо переходить к индексам и итераторам, но, внезапно, мы сразу теряем в эффективности работы

                                                      +2

                                                      Только в сорсах ioq3 индекс с выходом за пределы массива используется для проверки указателя на выход за пределы массива. Rust проверяет это в рантайме. Проще при рефакторинге перейти как минимум на индексы, а не указатели, это раз, а второе — работа с такими абстракциями, как итераторы, в Rust — это zero-cost.
                                                        0

                                                        аргумент принимается )

                                                        +1

                                                        nullptr — это таки другое.
                                                        Ну и NULL :


                                                        • не обязан быть 0
                                                        • 0 не обязательно невалидный указатель
                                                          0

                                                          Абсолютно верное замечание, но тогда надо четко отделять — мы про С или с++ говорим и какого стандарта )

                                                        +2
                                                        if (p >= &array[1024]) {
                                                           // error...
                                                        }

                                                        это они так выход за границы массива проверяют.
                                                          +1

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

                                                            0
                                                            Только в исходниках это используется не в каких-то массивах, находящихся в структурах, а в тех, что лежат в локальном стеке функции. Работать в этом случае с такими массивами через указатели — не очень ок.
                                                          0
                                                          А бенчмарки есть растовой и исходной версии?
                                                            0
                                                            На реддите обсуждалось. Авторы не делали никаких замеров. Ограничились тем что версия на расте выдаёт стабильные 90 fps. Там где-то в настройках стоит лок на максимальный fps

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

                                                          Самое читаемое