Наша команда 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"
.
L-- build
L-- debug-linux-x86_64
+-- baseq3
¦ +-- cgamex86_64.so # client
¦ +-- qagamex86_64.so # game server
¦ L-- uix86_64.so # ui
+-- ioq3ded.x86_64 # dedicated server binary
+-- ioquake3.x86_64 # main binary
+-- renderer_opengl1_x86_64.so # opengl1 renderer
L-- 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
L-- 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
и оставшиеся шаги, описанные выше. Комментарии (70)
midday
08.01.2020 21:23+7Объясните мне пожалуйста, а зачем это всё?
mapron
08.01.2020 21:35+5Исследовательский проект, можно понять много всякого, пока он делается. Так же спрашивали «на кой черт пыжиться kernel CLang-ом собирать, есть же GCC». Узнать что-то новое о наших инструментах)
Ну и возможно кому-то на практике пригодится тулза которая конвертит исходники на C, например, какой-нибудь видео encoder, скажем, AV1 ;)domix32
08.01.2020 23:04+2Что Сишный, что ржавый вариант на 60% состоят из ассемблерных вставок под платформенные SSE. Это скорее что-то из разряда "как не переписать все на Rust". Есть какая-то легаси либа в проде, которая вроде и работает, но код ужасно пахнет, тестов толком не было и допилить что-то к ней надо. Скармливаем в C2Rust, допиливаем напильником и запиливаем недостающие фичи и используем как drop-in решение.
crea7or
08.01.2020 23:14+4Такое как раз лучше и не трогать или уж переписывать как надо. Смысла в конвертации около нуля и +N новых багов.
domix32
09.01.2020 00:25+1Я имел ввиду ситуацию, когда к этому нужно новый функционал. Или есть какие-то странные плавующие SEGFAULT. Или есть завязки на другие либы, которые уже работают, но не имеют порта (типа SDL какой-нибудь для геймдева).
А вообще было бы неплохо если б кто-нибудь взялся перевести пару постов по теме (раз, два). В первой статье конвертация помогла избавиться от древненего незадокументированного косяка, как пример. Где-то тут же на хабре была статья про сферический анализатор текстов в типографии, который надо допиливать и в Си-лэнде оно оставляет много проблем как с IO так и с доступом к памяти, но это уже про полноценный RIIR, не про конвертацию.fougasse
09.01.2020 14:58А что, конвертация в Rust настолько умная, что поможет с плавающими SEGFAULT в С?
Whuthering
09.01.2020 16:15Учитывая, что там в сгенеренном коде unsafe на unsafe'е сидит, и unsafe'ом погоняет (что, впрочем, было ожидаемо) — то никак не поможет.
domix32
09.01.2020 22:33Сорян, ошибся, это со второй статьи. Там тоже 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
mistergrim
09.01.2020 04:43Что? Это та сотня строк асма, которая компилируется лишь в случае целевой платформы x86?
domix32
09.01.2020 22:26+1https://github.com/xiph/rav1e
Assembly 57.2%
Rust 42.5%
Other 0.3%
sloc на репу не травил, поэтому сколько сотен строк asm там не знаю. в сишной имплиментация похожая история.lieff
09.01.2020 22:37Там еще часть раст кода https://github.com/xiph/rav1e/tree/master/src/asm/x86 — unsafe функции из интринзиков.
mistergrim
10.01.2020 04:08А руками исходник-то не смотрели (ох, люблю я русский язык)?
Или, может, там под assebmly другое подразумевается?
Вообще-то Кармак не любил ассемблер начиная с первого Doom, даже там его самую чуточку.domix32
11.01.2020 00:27Ну, так кармак игры писал, а не выжимал из железа каждый бит. Производительность кода — скорее побочный эффект, необходимый для комфортной игры, нежели цель. У кодеков же бенчмаркинг едва ли не такты считает ради оптимальнейшей утилизации ресурсов железа, чтоб обработка чуть ли не в скорость шины данных упиралась, энергоэффективность тут же рядом.
Да, исходники смотрел, но по большей части уделял ржавой части и arm neon. Не слишком детально, конечно.
У сишного dav1d cхожие автометрики. Нюанс в том, что ассемблерный код не слишком компактный и десяток строк высокоуровневого языка вполне может вылиться в пару сотен строк на asm, а метрики gitlab и github почти наверняка ориентируются именно на sloc ничего удивительного, что "10 строк" asm не очень десять, а половина значимых возвратов каретки в репе
Lure_of_Chaos
09.01.2020 09:16Интересно, пришло ли подтверждение того, что Rust действительно легче в написании и поддержке, безопаснее и т.д.?
tbl
09.01.2020 15:37+2Код покрыт ансейфами абсолютно весь:
$ find ~/prog/ioq3 -name "*.rs" | xargs cat | grep unsafe | wc -l
9241
Ну это так, чтобы оценить масштаб проблемы$ find ~/prog/ioq3 -name "*.rs" | xargs cat | grep fn | wc -l
9740fougasse
09.01.2020 20:16Это игрушки из серии C++ в Java получаются — никакого коммерческого смысла подробные конвертации не имеют.
Что, в принципе, и ожидалось.
Cerberuser
10.01.2020 06:29<offtop>
Код покрыт ансейфами абсолютно весь
Пропел на мотив "Острова невезения". Захотелось записать переделку. Хабр, ты что творишь...
atri1
08.01.2020 22:09прочитал
C2Rust — фреймворком миграции, берущим на себя всю рутину миграции на Rust.
на самом деле это очень круто!
Только вот, с одной стороны-это позволит перенести нужные маленькие библиотеки в исходниках на Ruct чтоб не мучиться с бинарными биндингами(это ад полный) и не собирать на нескольких компиляторах проект.
С другой стороны Раст призван вывести Си программистов из зоны комфорта чтоб те перестали кодить устаревшими патернами и перешли на новую логику, и получили прирост производительности...
proninyaroslav
08.01.2020 23:24+1Пока есть много спецов, которые умеют справлятся с C и C++ и нужны как для новых, так и для существующих проектов, пока индустрия не начнёт осознавать что проблемы с этими языками куда дороже чем поиск/обучение специалистов на раст, то ожидать перехода не стоит. Тем более что индустрия C/C++ куда более консервативна и фундаментальна, чем прикладное ПО или веб, где сейчас ты пишешь на React JS, а завтра уже пишешь на Dart или ClojureScript, так как это хайпово, а бизнес требует выпускать всё больше новых и модных продуктов в короткие сроки. В системной же нише решения создаются на годы вперёд и держат над собой все эти прикладные вещи, и переход на новый язык требует оценки рисков, а сама технология и языки — долгой, многолетней «обкатки», после которой он получит признание. И то, в лучшем случае только для новых проектов, так как переписывать старые редко имеет смысл.
crea7or
08.01.2020 23:34А ещё MS задумал свой раст с преферансом ну и так далее. Вот они смогут толкнуть его(свой вариант).
gecube
08.01.2020 23:48+1 стандарт? Не, спасибо, нам такого не надо — раста достаточно.
Oplkill
09.01.2020 16:57у раста специфический синтаксис.
Вот если бы сделали удобный C-шный синтаксис, другое дело
Starl1ght
09.01.2020 20:31как раз было бы счастье, если бы MS сделал нормальный тулинг и нормальный синтаксис, да и своим примером показал как они с сишечки переезжают
freecoder_xx
10.01.2020 23:49+2Синтаксис Раста кажется страшным только до тех пор, пока вы не начнете писать на нем программы )
proninyaroslav
08.01.2020 23:50+1С их слов это не форк языка (надеюсь), но сам факт того, что индустрия начала интересоваться — хороший знак. Прошлой осенью ещё предлагали писать драйвера для линукс ядра. Хоть это и будет опциональная вещь, но она явно создаст первый крупный прецедент.
balajahe
09.01.2020 15:58Проект верона… они ругали раст за медленную скорость разработки, вот-вот только асинки появились, а в шарпе они были когда. Просто Rust — это коллективный продукт, решения принимаются медленно но верно, а корпорация, конечно, может быстрее в десять раз сделать, лишь бы не испортила.
crea7or
09.01.2020 23:14я надеюсь только на с лайк синтаксис, мне растовый не нравится.
balajahe
09.01.2020 23:28Сейчас немодно писать тип впереди переменной, все новые языки пишут через двоеточие после, а для функции через стрелку, как же можно идти против тренда :)
nlinker
10.01.2020 09:55Это связано не с трендом, а с выводом типов в различных контекстах (объявление локальной переменной — это лишь один из них).
Когда создавали C, о выводе типов не задумывались, а когда задумались, то позже, в C++, вынуждены были лепить костыли.balajahe
10.01.2020 10:25Длинный женерик указывать перед именем, да еще через пробел — это понятно, очень нечитабельно, особенно если перед именем функции, кому такое может понравиться, а про вывод типа не понял.
KanuTaH
10.01.2020 13:55+1Видимо, имеются в виду случаи типа такого, когда return type функции выводится из типов ее аргументов. Для подобных случаев в C++ тоже сейчас добавили стрелочный синтаксис.
tbl
10.01.2020 14:23+1в С++14 такое есть не только для лямбд с их стрелочным синтаксисом, но и для обычных функций:
template <typename T1, typename T2> auto add(T1 lhs, T2 rhs) { return lhs + rhs; }
KanuTaH
10.01.2020 14:35Ну это хорошо, конечно, если тело функции идет тут же, а если это предварительное объявление?
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);
прокатит без проблем.tbl
10.01.2020 14:44вопрос в рамках самообразования, а почему такое нельзя?
template <typename T1, typename T2> decltype(lhs + rhs) add(T1 lhs, T2 rhs);
KanuTaH
10.01.2020 14:48+1Ну, очевидный ответ — потому, что на момент парсинга конструкции с decltype про lhs и rhs еще ничего не известно, так как они объявлены позже. Менее очевиден ответ на вопрос, почему на такой случай не сделали какое-нибудь «исключение из правил» для парсера, этого я точно не знаю, не берусь ничего утверждать.
tbl
09.01.2020 12:06+1Посмотрел ioquake3/src/server/sv_init.rs, вспомнил выражение:
Программист на Фортран может писать на Фортране на любом языке программирования.
b0r1s
09.01.2020 12:07-6Мало им «окисления» Firefox, rsvg, теперь за ioQuake взялись, вместо того чтобы Crates.IO сделать offline-совместимым как PyPi, cpan.
Нужно по возможности защитить весь Open-Source софт от этой заразы путем форка.mistergrim
09.01.2020 13:03+1Это что, есть порты Doom и Quake на дельфи. И все живы-здоровы, никто не умер.
b0r1s
09.01.2020 15:29В случае с Delphi это были эксперименты по портированию. Игры с портированием на Rust заканчиваются тем что на него переключают основную ветку разработки, посмотрите на теже Firefox и rsvg, новые версии без Rust просто не собираются.
domix32
11.01.2020 00:40Ну, так у них и боль это баги безопасности из-за нарушения целостности памяти, а не offline хранилища. Поддержку, кастомных источников крейтов, кстати, уже завезли.
b0r1s
11.01.2020 01:53-2Отличная новость! Если есть решение, значит проблема таки была.
Нажать на кнопку «Нравится» не могу, заминусовали.
Кто наставил минусов, убирайте, я был прав, указанная мной проблема с использованием Rust в оффлайне имела место!
Cerberuser
09.01.2020 14:26А где же фраза "го вместе защищать, я создал"?
b0r1s
09.01.2020 16:49Вместе не получится. Многим не до этого, много народа на винде сидит им плевать на опенсорс.
Лично я играю в Urban Terror, который на ioquake3 крутится. Каждую неделю, каждый месяц, каждый год к ряду играю — это уже часть моей жизни.
inferrna
09.01.2020 13:38Если копнуть C2Rust чуть глубже, то можно обнаружить, что там есть инструменты для полуавтоматического переноса результирующего кода на идиоматичный раст. Но авторы решили не заморачиваться, а жаль.
TotalAMD
09.01.2020 17:00А зачем может быть нужен указатель за пределами массива?
gecube
09.01.2020 17:04указатель в принципе может указывать на ЛЮБОЙ адрес в памяти. NULL — так-то это тоже адрес ЗА пределами массива. Так что если так рассуждать, то от указателей надо переходить к индексам и итераторам, но, внезапно, мы сразу теряем в эффективности работы
tbl
09.01.2020 18:11+2
Только в сорсах ioq3 индекс с выходом за пределы массива используется для проверки указателя на выход за пределы массива. Rust проверяет это в рантайме. Проще при рефакторинге перейти как минимум на индексы, а не указатели, это раз, а второе — работа с такими абстракциями, как итераторы, в Rust — это zero-cost.
zagayevskiy
09.01.2020 17:17+2if (p >= &array[1024]) { // error... }
это они так выход за границы массива проверяют.
mayorovp
09.01.2020 17:22+1Потому что иногда (на самом деле часто) имеет смысл рассматривать указатели как указывающие не на элемент массива, а на границу между элементами. А таких границ всегда можно провести на одну больше, чем в массиве элементов...
tbl
09.01.2020 18:16Только в исходниках это используется не в каких-то массивах, находящихся в структурах, а в тех, что лежат в локальном стеке функции. Работать в этом случае с такими массивами через указатели — не очень ок.
fcoder
До "собирается на современных платформах" всё же ещё далеко, пока что этот форк работает только под линукс
youROCK
ioquake3 должен работать на всех платформах, согласно README вот тут: https://github.com/ioquake/ioq3. Совершенно точно он работает под macOS.
fcoder
Исходный ioquake — вполне собирается и под windows. Но сравнивая репозитории очевидно, что winows-specific вещи исключены
V1tol
В исходном коде платформо-специфичные вещи исключили, потому что форк базируется на SDL2. Этот порт точно работал под Windows ещё пару лет назад — сам качал тестовые билды с сайта форка, чтобы никого не обделить на lan party в офисе ). Сейчас раздел пустой из-за взлома сборочной инфраструктуры, поэтому придётся собирать самому.