Вопросы на собеседовании: Rust-разработчик
100 реальных вопросов с образцовыми ответами и пояснениями для уровня Junior Rust-разработчик.
Смотреть пример резюме: Rust-разработчик →Тренировка флешкарточками
Интервальное повторение · Hunter Pass
Вопросы
Переменной нельзя присвоить новое значение, если её binding не объявлен с mut.
- let count = 1 создаёт неизменяемый binding, поэтому count = 2 будет отклонено.
- let mut count = 1 разрешает последующее присваивание, но тип значения остаётся прежним.
- Неизменяемость по умолчанию делает предполагаемые изменения состояния явными и понятными.
Зачем это спрашивают: Интервьюер проверяет, понимаете ли вы поведение bindings по умолчанию и назначение mut.
Shadowing создаёт новый binding с тем же именем, а mut разрешает менять значение одного существующего binding.
- Новый let может изменить тип значения, потому что вводит отдельную переменную.
- Присваивание в mutable-binding обязано сохранять тип, установленный при объявлении.
- Shadowing удобен для последовательных преобразований, когда старое значение больше не нужно.
Зачем это спрашивают: Сильный ответ отличает повторное связывание от мутации и объясняет, почему shadowing может менять тип.
К скалярным типам Rust относятся целые числа, числа с плавающей точкой, логические значения и символы.
- Целые типы включают знаковые i8 до i128 и isize, а также беззнаковые u8 до u128 и usize.
- Числа с плавающей точкой используют f32 или f64, а bool содержит true или false.
- char является четырёхбайтовым скалярным значением Unicode, а не одним байтом UTF-8.
Зачем это спрашивают: Интервьюер оценивает знание базовых типов, включая важное различие между char и байтом.
usize является естественным типом для размеров памяти и индексов коллекций, потому что его ширина совпадает с шириной указателя целевой платформы.
- Для индексации срезов и массивов требуется usize.
- Тип фиксированной ширины, например u32, лучше подходит формату файла или протоколу, где нужно ровно 32 бита.
- Преобразования между целыми типами должны быть явными, поскольку значение может не поместиться в целевой тип.
Зачем это спрашивают: Вопрос проверяет, связываете ли вы выбор целого типа с индексами, шириной платформы и внешними форматами данных.
Кортеж хранит фиксированное число значений, которые могут иметь разные типы, а массив хранит фиксированное число значений одного типа.
- Элементы кортежа читаются по постоянным позициям вроде value.0 или через деструктуризацию.
- Тип массива включает тип элемента и длину, например [u8; 16].
- Массивы хранятся как inline-значения и могут заимствоваться как срезы для API с переменной длиной данных.
Зачем это спрашивают: Интервьюер проверяет понимание базовых составных типов Rust и их структуры на уровне типов.
Statement выполняет действие без пригодного для использования значения, а expression вычисляется в значение.
- Вызовы функций, блоки match и if, а также обычные блоки могут быть expressions.
- Последний expression блока становится значением блока, если после него нет точки с запятой.
- Добавление точки с запятой обычно превращает expression в statement со значением unit-типа ().
Зачем это спрашивают: Сильный ответ объясняет, почему функции и блоки Rust возвращают значения без явного ключевого слова return.
Владение позволяет Rust управлять памятью и предотвращать некорректный доступ без сборщика мусора.
- У каждого значения есть владелец, который определяет момент удаления значения.
- Присваивание и вызов функции могут передавать владение, после чего старый binding становится недоступен.
- Заимствование даёт временный доступ без передачи владения.
Зачем это спрашивают: Интервьюер хочет увидеть ясную модель, связывающую владение с memory safety, очисткой ресурсов и заимствованием.
У каждого значения есть один владелец, одновременно владелец может быть только один, а при выходе владельца из области видимости значение удаляется.
- Владение non-Copy значением может перейти от одного binding к другому.
- Ссылка заимствует значение и не становится его владельцем.
- Компилятор проверяет эти правила до запуска программы.
Зачем это спрашивают: Интервьюер проверяет, можете ли вы точно сформулировать модель владения, а не только назвать её защитой памяти.
При присваивании String владение обычно перемещается в новый binding.
- String владеет данными в куче, поэтому копирование только его stack-метаданных между двумя владельцами создало бы риск двойного освобождения.
- После move исходный binding использовать нельзя, если владение не было возвращено.
- Метод clone создаёт независимый String, когда оба значения действительно нужны.
Зачем это спрашивают: Сильный ответ связывает move-семантику с выделением памяти String и объясняет недоступность исходного binding.
Передача аргумента следует тем же правилам, что и присваивание: non-Copy значения перемещаются, а Copy значения копируются.
- Функция, принимающая String по значению, владеет им и может удалить его до возврата.
- Параметр &str или &String заимствует данные, а не забирает значение у вызывающей стороны.
- Возврат значения может передать владение обратно, но заимствование обычно яснее, если потребление не требуется.
Зачем это спрашивают: Интервьюер оценивает, можете ли вы предсказать передачу владения через простую границу функции.
Copy означает, что присваивание и передача аргумента неявно дублируют значение вместо его перемещения.
- Простые скалярные типы и кортежи только из Copy-значений обычно реализуют Copy.
- Тип с реализацией Drop не может реализовать Copy, поскольку неявное дублирование конфликтовало бы с уникальной очисткой.
- Copy предназначен для дешёвого побитового дублирования и должен быть явно реализован или выведен для пользовательского типа.
Зачем это спрашивают: Интервьюер проверяет понимание семантического эффекта и важных ограничений Copy.
Clone выполняет явное дублирование через метод clone, а Copy разрешает неявное дублирование при присваивании и передаче аргумента.
- Clone может выделять память или выполнять специальную логику, как при копировании буфера String.
- Каждый Copy-тип обязан реализовывать Clone, но многие Clone-типы не являются Copy.
- Вызов clone должен выражать реальную потребность во втором owned-значении, а не автоматически заглушать ошибку владения.
Зачем это спрашивают: Сильный ответ отличает явное потенциально дорогое дублирование от неявного дешёвого копирования.
Значение удаляется при выходе владельца из области видимости, если до этого владение не было перемещено.
- Rust вызывает реализацию Drop, а затем освобождает принадлежащие значению ресурсы.
- Локальные переменные обычно удаляются в порядке, обратном их созданию.
- Функция std::mem::drop позволяет явно завершить владение до конца окружающей области.
Зачем это спрашивают: Интервьюер проверяет базовое понимание детерминированной очистки ресурсов и перемещённых значений.
Заимствование даёт временный доступ к значению через ссылку без передачи владения.
- Общая ссылка имеет тип &T и позволяет читать заимствованное значение.
- Изменяемая ссылка имеет тип &mut T и позволяет менять заимствованное значение.
- Владелец остаётся ответственным за удаление значения после завершения всех заимствований.
Зачем это спрашивают: Интервьюер хочет увидеть прямое различие между временным доступом и передачей владения.
В один момент Rust разрешает либо любое число общих ссылок, либо одну изменяемую ссылку на одно значение.
- Общие ссылки разрешают одновременное чтение, но не мутацию через эти ссылки.
- Эксклюзивная изменяемая ссылка не даёт другим ссылкам наблюдать значение во время изменения.
- Эти правила предотвращают гонки данных и инвалидированные ссылки при компиляции.
Зачем это спрашивают: Сильный ответ формулирует правило эксклюзивности и связывает его с обеспечиваемой безопасностью.
Мутация через &mut T разрешена, потому что ссылка даёт эксклюзивный изменяемый доступ к целевому значению.
- Исходное значение должно быть объявлено с mut, прежде чем код сможет взять на него изменяемую ссылку.
- Перепривязка переменной с изменяемой ссылкой отличается от мутации значения по этой ссылке.
- Параметр функции value: &mut T может менять T без получения владения.
Зачем это спрашивают: Интервьюер проверяет, различаете ли вы изменяемую цель, изменяемую ссылку и изменяемый binding.
Rust отклоняет ссылку, которая может пережить своё значение, потому что её использование обратилось бы к недействительной памяти.
- Локальное значение удаляется, когда его владелец покидает область видимости.
- Возврат ссылки на такое значение оставил бы вызывающую сторону с указателем на освобождённое хранилище.
- Обычное решение состоит в возврате owned-значения, владение которым корректно переходит к вызывающей стороне.
Зачем это спрашивают: Интервьюер оценивает, понимаете ли вы причину проверки lifetime с точки зрения безопасности памяти.
Lifetime является областью кода, в которой ссылка остаётся действительной.
- Компилятор сравнивает lifetimes, чтобы заимствованное значение жило дольше каждого использования ссылки.
- Большинство lifetimes выводятся и не требуют письменной аннотации.
- Lifetime-аннотация описывает отношения между ссылками, но не продлевает жизнь значений.
Зачем это спрашивают: Сильный ответ определяет lifetimes как отношения действительности, а не runtime-таймеры или продление памяти.
Аннотация нужна, когда компилятору требуется знать, с каким входным заимствованием связан возвращаемый reference.
- Сигнатура fn longest<'a>(x: &'a str, y: &'a str) -> &'a str указывает, что результат действителен в их общей доступной области.
- Аннотация не требует, чтобы оба аргумента имели буквально одинаковые lifetimes.
- Она не позволяет использовать возвращённую ссылку дольше, чем разрешает любой из возможных источников.
Зачем это спрашивают: Интервьюер проверяет, умеете ли вы читать простое lifetime-отношение в сигнатуре функции.
Lifetime-параметр фиксирует, что экземпляр struct не может пережить данные, на которые ссылается одно из его полей.
- Поле вида text: &'a str связывает действительность поля с 'a.
- Компилятор не даст использовать struct после удаления источника text.
- Владение String вместо ссылки убирает это отношение заимствования, но меняет выделение памяти и семантику владения.
Зачем это спрашивают: Вопрос проверяет понимание того, как borrowed-поля ограничивают lifetime содержащего их struct.
Закрытые вопросы
- 21
Что означает lifetime 'static?
lifetimes - 22
Как определить и создать struct в Rust?
types - 23
Как struct update syntax влияет на владение?
ownershiptypes - 24
Чем associated function отличается от method в блоке impl?
types - 25
Что такое tuple structs и unit-like structs?
types - 26
Почему enum в Rust мощнее простой числовой перечислимости?
enums - 27
Почему выражение match должно быть исчерпывающим?
control-flowbasics - 28
Как patterns деструктурируют значения в match arm?
destructuringcontrol-flow - 29
Когда if let предпочтительнее match?
control-flow - 30
Что означают _ и .. в patterns Rust?
- 31
Что представляет Option<T>?
error-handling - 32
Для чего используются map, and_then и unwrap_or у Option<T>?
error-handling - 33
Что представляет Result<T, E>?
error-handling - 34
Как map и map_err преобразуют Result?
error-handling - 35
Что оператор ? делает с Result?
error-handling - 36
Как оператор ? ведёт себя с Option?
error-handling - 37
Когда функция Rust должна возвращать Result вместо panic?
error-handlingerrors - 38
Чем unwrap отличается от expect?
error-handling - 39
Что такое трейт в Rust?
traits - 40
Как реализовать трейт для типа?
traits - 41
Что такое trait bound для generic-параметра типа?
genericstraits - 42
Что #[derive(...)] делает для трейтов?
traits - 43
Что такое Vec<T> и чем он отличается от массива?
- 44
Что такое срез в Rust?
types - 45
Чем String отличается от &str?
- 46
Почему Rust не разрешает индексировать String целым числом?
indexes - 47
Для чего используются Cargo и Cargo.toml?
cargo - 48
Чем package, crate и module отличаются в Rust?
cargo - 49
Что делают pub и use в модульной системе Rust?
system-design - 50
Какие роли Cargo.toml и Cargo.lock играют в управлении зависимостями?
dependenciescargo - 51
Функция передаёт String во вспомогательную функцию логирования, а затем снова использует строку; как избежать ненужного перемещения?
logging - 52
Как спроектировать функцию, которая только читает String вызывающего кода?
design - 53
Как написать функцию, которая добавляет текст в принадлежащий вызывающему коду String?
- 54
Borrow checker запрещает две одновременные изменяемые ссылки на один Vec; как исправить код?
ownership - 55
Первый элемент Vec заимствован неизменяемо, после чего код вызывает push; как устранить ошибку?
immutabilityownership - 56
Функция пытается вернуть &str, указывающий внутрь локального String; как это исправить?
- 57
Код перемещает поле String из структуры, а затем использует структуру целиком; как избежать частичного перемещения?
types - 58
Цикл передаёт каждый String во владение вспомогательной функции, а затем снова использует значение; как изменить функцию или цикл?
- 59
Как прочитать необязательный аргумент командной строки и использовать значение по умолчанию при его отсутствии?
error-handling - 60
Как преобразовать Option<String> в Option<usize> с длиной строки?
error-handlingtypes - 61
Как получить порт из Option<u16>, используя 8080 при отсутствии настройки?
configerror-handling - 62
Как преобразовать Option<String> в число, сохраняя неверный или отсутствующий ввод как None?
error-handling - 63
Как использовать ? в функции, возвращающей Option<T>?
error-handling - 64
Как преобразовать строку в i32 и вернуть ошибку парсинга вызывающему коду?
- 65
Как использовать ? при чтении файла и парсинге содержимого в функции с результатом Result?
error-handling - 66
Функция использует ? для двух операций с разными типами ошибок; как сделать их совместимыми?
- 67
Как обработать ошибку NotFound при открытии файла иначе, чем остальные ошибки?
- 68
Продакшн-код вызывает unwrap для пользовательского ввода; как его улучшить?
error-handling - 69
Как собрать только чётные числа из Vec<i32> в новый Vec?
- 70
Как создать Vec<usize> с длинами заимствованных строк, не потребляя их?
ownershiptypes - 71
Как подсчитать частоту слов с помощью HashMap?
- 72
Как вставить вычисляемое значение в HashMap только при отсутствии ключа?
- 73
Как прочитать элемент Vec по введённому пользователем индексу без риска паники?
indexeserrors - 74
Как отсортировать Vec записей по целочисленному полю, сохранив сами записи?
- 75
Как удалить соседние дубликаты из Vec и что делать, если одинаковые значения разбросаны?
- 76
Как просуммировать только положительные значения из итератора i32?
iterationiterators - 77
Как найти первого пользователя с нужным ID в Vec<User>?
control-flow - 78
Как вывести каждый элемент коллекции вместе с его индексом от нуля?
- 79
Как проверить, есть ли среди значений хотя бы одно неверное и верны ли все значения?
- 80
Как объединить имена и оценки из двух векторов в пары?
- 81
Как обрабатывать срез фиксированными группами по три элемента без ручной арифметики индексов?
concurrencytypesindexes - 82
Как разобрать строку с разделителями-запятыми на непустые поля без пробелов по краям?
- 83
Как объединить Vec<String> в одну строку с разделителями-запятыми?
joins - 84
Как безопасно получить первое скалярное значение Unicode из UTF-8 String?
types - 85
Как выполнить разное поведение для вариантов Create, Delete и Quit enum команды?
enums - 86
Как выполнить код только при наличии значения в Option и проигнорировать None?
error-handling - 87
Как извлекать значения из Vec, пока он не опустеет?
hypothesis-testing - 88
Как деструктурировать Point { x, y }, принимая только точки с положительным y?
destructuring - 89
Как сопоставить код статуса по диапазонам успеха, клиентской ошибки, серверной ошибки и остальных значений?
httpstatus-codescontrol-flow - 90
Как вернуть из небольшой библиотечной функции разные ошибки NotFound и InvalidInput?
distinct - 91
Как добавить путь файла в ошибку консольного приложения с помощью anyhow?
error-handling - 92
Как десериализовать JSON-строку в структуру конфигурации Rust с помощью serde?
configtypes - 93
Поле JSON может отсутствовать; как представить это с serde?
- 94
Как написать модульный тест для функции сложения двух чисел?
unit - 95
Как тестировать функцию с результатом Result без слепого вызова unwrap?
error-handling - 96
Какие проверки Cargo нужно запустить перед отправкой изменения и как реагировать на сбои?
cargo - 97
Как вызвать async-функцию из небольшой консольной программы на Tokio?
async-runtimeasync - 98
tokio::spawn отклоняет задачу, заимствующую локальный String; как исправить ошибку владения?
ownershipasync-runtime - 99
Как запустить две независимые async-операции конкурентно и дождаться обоих результатов?
asyncconcurrencyerror-handling - 100
Как не допустить бесконечного ожидания асинхронной сетевой операции в Tokio?
async-runtimeasync