сеньорчикОткрыть в Telegram
← все вопросывопросы для собеседований · unsafe и производительность Rust

Вопросы по unsafe и производительности Rust на собеседовании

Блок отделяет тех, кто читал про язык, от тех, кто отлаживал его в проде. Здесь проверяют, понимаешь ли ты, что unsafe не выключает проверки заимствования, а лишь разрешает пять конкретных операций, и умеешь ли ты доказывать выигрыш замером, а не ощущением.

72 вопросов в банке·6 подтем·ниже разбор 9

Что спрашивают

Из чего состоит тема

Так тема разложена в тренажёре: движок ведёт прогресс по каждой подтеме отдельно и возвращает те, где вы ошибаетесь.

Разборы подтем

Конспект по каждой: что это, как отвечать вслух, на чём валятся, плюс вопросы для самопроверки.

Примеры вопросов с разбором

  1. #rs_bench_profile1 / 9
    Почему бессмысленно измерять производительность на debug-сборке?
    A)В debug добавлены счётчики времени, искажающие замер
    B)Debug использует другой аллокатор памяти
    C)Оптимизации выключены, разница с release — десятки раз
    D)В debug код выполняется интерпретатором, а не машинными инструкциями
    показать ответ и разбор
    +C)Оптимизации выключены, разница с release — десятки раз

    // разбор: В debug нет инлайнинга и оптимизаций, зато есть проверки переполнения — абстракции, бесплатные в release, здесь стоят реальных денег. Замеры дают не просто смещённые числа, а другой порядок и другой профиль: горячим окажется совсем не то место. Мерить нужно release, а лучше тем же профилем, что уходит в прод.

  2. #rs_ffi2 / 9
    Зачем структуре, уезжающей в C-библиотеку, атрибут #[repr(C)]?
    A)Он переводит имена полей в кодировку, понятную C
    B)Он запрещает компилятору добавлять байты выравнивания между полями
    C)Он делает структуру переносимой между языками через сериализацию
    D)Он фиксирует порядок полей и выравнивание как в C
    показать ответ и разбор
    +D)Он фиксирует порядок полей и выравнивание как в C

    // разбор: Раскладка по умолчанию не определена: компилятор вправе переставлять поля, чтобы уплотнить структуру. Для обмена с C это недопустимо, поэтому repr(C) фиксирует порядок объявления и выравнивание по правилам C. Убирает промежутки другой атрибут — repr(packed), и он опасен: чтение невыровненного поля стоит дороже, а ссылку на него брать нельзя.

  3. #rs_memory_layout3 / 9
    Структура { a: u8, b: u32, c: u8 } занимает 8 байт, а с #[repr(C)] — 12. Почему?
    A)repr(C) выравнивает структуру по границе 16 байт
    B)repr(C) добавляет заголовок с информацией о типе
    C)Без repr поля хранятся упакованно, без выравнивания
    D)Без repr компилятор переставил поля и уплотнил раскладку
    показать ответ и разбор
    +D)Без repr компилятор переставил поля и уплотнил раскладку

    // разбор: В раскладке по умолчанию порядок полей не гарантирован: компилятор ставит u32 первым, два u8 — следом, и всё влезает в 8 байт. repr(C) обязан сохранить порядок объявления, поэтому между a и b появляются три байта выравнивания, а в конце — ещё три. Выигрыш обычной раскладки — как раз в свободе перестановки.

  4. #rs_perf4 / 9
    Что означает лозунг zero-cost abstractions?
    A)Абстракции не занимают память в рантайме
    B)Код на Rust работает быстрее аналога на C
    C)Компилятор убирает неиспользуемые абстракции из бинарника
    D)Абстракция не дороже такого же кода, написанного руками
    показать ответ и разбор
    +D)Абстракция не дороже такого же кода, написанного руками

    // разбор: Формулировка Страуструпа: за то, чем не пользуешься, не платишь, а тем, чем пользуешься, не написал бы лучше руками. Итератор с map и filter разворачивается в тот же цикл, дженерик мономорфизируется в прямые вызовы, Option<Box<T>> занимает столько же, сколько указатель. Но нулевая цена не универсальна: dyn Trait, Rc и RefCell стоят вполне заметно.

  5. #rs_raw_ptr5 / 9
    Какая операция с сырым указателем требует unsafe?
    A)Разыменование: создать и передать можно и без unsafe
    B)Печать адреса через Debug-форматирование
    C)Создание из ссылки: именно тут теряются гарантии
    D)Сравнение двух указателей на равенство
    показать ответ и разбор
    +A)Разыменование: создать и передать можно и без unsafe

    // разбор: Пока указатель просто лежит в переменной, ничего опасного не происходит: его можно создать из ссылки, скопировать, сравнить, напечатать. Опасно обращение по нему — компилятор больше не знает, жив ли объект и не занят ли он кем-то ещё. Поэтому unsafe требуется ровно на разыменовании.

  6. #rs_unsafe6 / 9
    Что именно разрешает блок unsafe?
    A)Действия с памятью в обход проверок компилятора
    B)Пять операций: сырые указатели, unsafe-функции, static mut, union, трейты
    C)Обход правил заимствования там, где программист уверен в корректности
    D)Прямой доступ к ассемблеру и системным вызовам ядра
    показать ответ и разбор
    +B)Пять операций: сырые указатели, unsafe-функции, static mut, union, трейты

    // разбор: Список закрытый: разыменовать сырой указатель, вызвать unsafe-функцию или функцию из FFI, обратиться к изменяемому static, прочитать поле union, реализовать unsafe-трейт. Всё остальное работает как обычно — проверка типов, владение и заимствование никуда не деваются. Отсюда правило: чем короче блок, тем точнее видно, что именно взято под ответственность.

  7. #rs_bench_profile7 / 9
    Бенчмарк показал ноль наносекунд на вычислении. Что случилось?
    A)Функция была выполнена на этапе компиляции как const
    B)Результат не использован — оптимизатор выбросил вычисление
    C)Компилятор закэшировал результат между итерациями цикла
    D)Таймер не видит интервалов короче микросекунды
    показать ответ и разбор
    +B)Результат не использован — оптимизатор выбросил вычисление

    // разбор: Классика микробенчмарков: значение никому не нужно, входные данные — константы, всё сворачивается ещё при сборке. Лечится black_box вокруг входа и результата: он прячет значения от оптимизатора. Именно поэтому берут criterion — там прогрев, статистика по множеству итераций и защита от свёртки уже встроены.

  8. #rs_ffi8 / 9
    Почему String нельзя передать в C-функцию как есть?
    A)String лежит в куче Rust, а C ожидает данные на стеке
    B)String хранит текст в UTF-16, C — в однобайтовой кодировке
    C)У C-строки нужен завершающий ноль, а String хранит длину отдельно
    D)Передача String нарушает владение: C освободит память чужим аллокатором
    показать ответ и разбор
    +C)У C-строки нужен завершающий ноль, а String хранит длину отдельно

    // разбор: String — это указатель, длина и ёмкость без завершающего нуля, а C ждёт указатель на последовательность байт, оканчивающуюся нулём. Для перехода есть CString (владеющая, с нулём на конце) и CStr (заимствованный вид). Дополнительная тонкость: внутри строки Rust ноль может встретиться, и тогда преобразование вернёт ошибку.

  9. #rs_memory_layout9 / 9
    Чем опасен #[repr(packed)]?
    A)Структура перестаёт работать с derive-макросами
    B)Поля переупорядочиваются непредсказуемо между сборками
    C)Поля перестают быть выровненными, ссылку на них не взять
    D)Компилятор запрещает класть такую структуру в Vec
    показать ответ и разбор
    +C)Поля перестают быть выровненными, ссылку на них не взять

    // разбор: Упаковка убирает промежутки, и поле u32 может оказаться по нечётному адресу. Ссылка обязана быть выровненной, поэтому &packed.field — ошибка компиляции; читать можно только копированием значения. На части архитектур невыровненный доступ ещё и дороже. Оправдано это в разборе бинарных форматов и протоколов, где раскладка задана извне.

это 9 из 72

Ещё 63 вопросов по теме — в тренажёре, с движком повторения

Прочитать разбор и ответить самому — разные навыки. В Сеньорчике вопросы идут сессиями, а движок возвращает подтемы, где вы ошибаетесь, пока они не начнут отскакивать. Бесплатно, лимит по энергии.

Частые вопросы