Вопросы по экосистеме Rust на собеседовании
Язык языком, а работать придётся с Cargo, serde и чужими крейтами, поэтому прикладной блок спрашивают почти всегда. Проверяют бытовую зрелость: понимаешь ли, как фичи складываются между зависимостями, где живут тесты и почему в проде фиксируют тулчейн.
Что спрашивают
- +Cargo: профили debug и release, фичи и их аддитивность, workspace и общий Cargo.lock, cargo add и update
- +Тесты: unit рядом с кодом и integration в tests/, doc-тесты как часть документации, подмена зависимостей трейтом
- +Макросы: macro_rules! против процедурных, derive и атрибуты, цена в времени компиляции, cargo expand
- +serde: derive и атрибуты rename и skip, работа с чужой схемой JSON, что делать с непостоянными типами
- +Веб и базы: axum и tower, sqlx с проверкой запросов на компиляции против diesel, пул соединений
- +Гигиена: clippy в CI, rustfmt, cargo audit и deny, miri для unsafe, фиксация версии через rust-toolchain
Из чего состоит тема
Так тема разложена в тренажёре: движок ведёт прогресс по каждой подтеме отдельно и возвращает те, где вы ошибаетесь.
- Cargo, фичи, workspace14
- clippy, fmt, miri13
- Тестирование13
- serde и сериализация12
- Веб-стек и базы12
- Макросы11
Разборы подтем
Конспект по каждой: что это, как отвечать вслух, на чём валятся, плюс вопросы для самопроверки.
- Тестирование в Rust13 вопросов
- Макросы в Rust11 вопросов
- serde: сериализация в Rust12 вопросов
- Веб-стек и базы в Rust12 вопросов
- clippy, rustfmt и miri13 вопросов
Примеры вопросов с разбором
- Зачем нужен Cargo.lock, если версии уже указаны в Cargo.toml?A)Он хранит контрольные суммы исходников для проверки лицензийB)Он фиксирует точные версии всего дерева зависимостейC)Он задаёт порядок сборки зависимостей в рабочем пространствеD)Он кэширует скачанные крейты для сборки без сети
показать ответ и разбор
+B)Он фиксирует точные версии всего дерева зависимостей// разбор: В Cargo.toml обычно стоит диапазон вроде "1.0", а lock записывает, какая именно версия каждого крейта была выбрана — включая транзитивные. Благодаря этому сборка воспроизводима у всех и в CI. Для приложений lock коммитят в репозиторий, для библиотек он на пользователей не влияет.
- Чем macro_rules! отличается от процедурного макроса?A)Первый сопоставляет образцы, второй — код над токенамиB)Первый умеет генерировать типы, второй — только выраженияC)Первый работает в рантайме, второй — на этапе компиляцииD)Первый доступен только внутри своего модуля, второй — публичный
показать ответ и разбор
+A)Первый сопоставляет образцы, второй — код над токенами// разбор: macro_rules! — это набор правил «такой образец превращается в такой код», декларативно и без отдельного крейта. Процедурный макрос — программа, которая получает поток токенов и возвращает новый: так работают derive, атрибуты вроде tokio::main и функциональные макросы. Оба разворачиваются при компиляции, но второй мощнее и заметно дороже по времени сборки.
- Что делает #[derive(Serialize, Deserialize)] из serde?A)Генерирует обход полей структуры для любого форматаB)Регистрирует структуру в глобальном реестре типов serdeC)Превращает структуру в JSON при каждом присваиванииD)Добавляет к структуре методы to_json и from_json
показать ответ и разбор
+A)Генерирует обход полей структуры для любого формата// разбор: serde разделяет модель данных и формат: derive пишет обход полей, а конкретный формат подключается отдельным крейтом — serde_json, bincode, serde_yaml. Поэтому одна структура сериализуется куда угодно без изменений, а работа с полями раскрывается в прямой код без рефлексии и без обхода по именам в рантайме.
- Чем юнит-тесты в модуле отличаются от тестов в каталоге tests/?A)Модульные запускаются командой cargo test, а тесты в tests/ — отдельной командойB)Тесты в tests/ выполняются последовательно, модульные — параллельноC)Модульные видят приватные элементы, тесты в tests/ — только публичный APID)Модульные попадают в релизный бинарник, тесты в tests/ — нет
показать ответ и разбор
+C)Модульные видят приватные элементы, тесты в tests/ — только публичный API// разбор: Модуль tests лежит внутри крейта, поэтому видит внутренности через use super::*. Каталог tests/ компилируется как отдельные крейты и работает с библиотекой снаружи — ровно как её будущий пользователь. Обе категории гоняет один cargo test, и обе не попадают в релизную сборку.
- Чем занимается clippy?A)Форматирует код по единому стилюB)Проверяет зависимости на известные уязвимостиC)Ищет неидиоматичные места сверх проверок компилятораD)Ищет неопределённое поведение в unsafe-коде
показать ответ и разбор
+C)Ищет неидиоматичные места сверх проверок компилятора// разбор: Это линтер с сотнями правил: лишний clone, ручной цикл вместо итератора, unwrap там, где просится разбор, подозрительные сравнения. Часть подсказок применяется автоматически через --fix. Форматированием занимается rustfmt, уязвимостями — cargo audit, а неопределённым поведением — Miri: инструменты разные.
- Чем sqlx отличается от diesel по подходу?A)sqlx синхронный, diesel асинхронныйB)sqlx проверяет SQL при компиляции, diesel даёт типизированный DSLC)sqlx требует миграций, diesel создаёт схему автоматическиD)sqlx работает только с PostgreSQL, diesel — со всеми базами
показать ответ и разбор
+B)sqlx проверяет SQL при компиляции, diesel даёт типизированный DSL// разбор: В sqlx пишут привычный SQL, а макрос query! сверяет его с реальной базой на этапе компиляции — нужен доступ к базе или сохранённые метаданные. Diesel вместо строк даёт типизированный конструктор запросов поверх схемы. Первый ближе тем, кто думает на SQL, второй ловит больше ошибок формой типов, но хуже переносит нестандартные запросы.
- Что означает запись зависимости serde = "1.2"?A)Ровно версию 1.2.0, без обновленийB)Последнюю доступную версию на момент сборкиC)Версии от 1.2.0 до 2.0.0, без мажорного обновленияD)Версии ветки 1.2.x: минорные обновления не подтянутся
показать ответ и разбор
+C)Версии от 1.2.0 до 2.0.0, без мажорного обновления// разбор: По умолчанию действует правило совместимости: строка равносильна ^1.2 — обновления в пределах мажорной версии считаются безопасными и подтягиваются, переход на 2.0 — нет. Точную версию задают через =1.2.0, а ветку — через ~1.2. Что именно выбрано, видно в lock-файле.
- Что означает гигиена макросов в Rust?A)Компилятор проверяет макрос на бесконечную рекурсиюB)Макрос не может изменять код за пределами своего вызоваC)Раскрытие макроса не зависит от порядка объявлений в файлеD)Переменные макроса не конфликтуют с местом вызова
показать ответ и разбор
+D)Переменные макроса не конфликтуют с местом вызова// разбор: Идентификаторы, введённые внутри макроса, живут в своей области: объявленный там let x не затрёт x у вызывающего и не будет им подменён. В C такие коллизии — классическая ловушка препроцессора, здесь их нет. Гигиена частичная: типы и функции по именам всё-таки резолвятся в контексте вызова, поэтому в макросах пишут полные пути.
- В JSON поле называется user_name, в структуре хочется userName. Что взять?A)Переименовать поле в структуре: serde сопоставляет только по именамB)Реализовать Deserialize вручную для этой структурыC)Добавить промежуточную структуру и конвертировать вручнуюD)Атрибут #[serde(rename_all = "camelCase")] или rename на поле
показать ответ и разбор
+D)Атрибут #[serde(rename_all = "camelCase")] или rename на поле// разбор: Атрибуты закрывают почти все расхождения между внешним форматом и внутренней моделью: rename и rename_all для имён, default для отсутствующих полей, skip_serializing_if для пустых, flatten для вложенности, with для своих преобразований. Ручная реализация нужна лишь в сложных случаях вроде разбора нестандартного формата даты.
это 9 из 75
Ещё 66 вопросов по теме — в тренажёре, с движком повторения
Прочитать разбор и ответить самому — разные навыки. В Сеньорчике вопросы идут сессиями, а движок возвращает подтемы, где вы ошибаетесь, пока они не начнут отскакивать. Бесплатно, лимит по энергии.
Частые вопросы
Чем doc-тест отличается от обычного?
Doc-тест живёт в комментарии документации и компилируется как отдельный крейт, использующий вашу библиотеку снаружи. Поэтому он одновременно проверяет и код примера, и то, что публичный API вообще пригоден к использованию так, как описан.
sqlx или diesel?
sqlx оставляет вам обычный SQL и умеет проверять запросы на этапе компиляции по живой схеме, diesel строит типобезопасный DSL и берёт на себя миграции и связи. Выбор обычно про то, хочет команда писать SQL руками или прятать его за абстракцией.
Зачем нужен miri?
Это интерпретатор, который выполняет код и ловит неопределённое поведение в unsafe: выход за границы, невыровненный доступ, нарушение правил заимствования на сырых указателях. Обычные тесты такие ошибки пропускают — они проявляются не всегда и не сразу.