Skip to content

Вопросы на собеседовании: Rust-разработчик

100 реальных вопросов с образцовыми ответами и пояснениями для уровня Junior Rust-разработчик.

Смотреть пример резюме: Rust-разработчик

Тренировка флешкарточками

Интервальное повторение · Hunter Pass

Вопросы

immutability

Переменной нельзя присвоить новое значение, если её binding не объявлен с mut.

  • let count = 1 создаёт неизменяемый binding, поэтому count = 2 будет отклонено.
  • let mut count = 1 разрешает последующее присваивание, но тип значения остаётся прежним.
  • Неизменяемость по умолчанию делает предполагаемые изменения состояния явными и понятными.

Зачем это спрашивают: Интервьюер проверяет, понимаете ли вы поведение bindings по умолчанию и назначение mut.

basics

Shadowing создаёт новый binding с тем же именем, а mut разрешает менять значение одного существующего binding.

  • Новый let может изменить тип значения, потому что вводит отдельную переменную.
  • Присваивание в mutable-binding обязано сохранять тип, установленный при объявлении.
  • Shadowing удобен для последовательных преобразований, когда старое значение больше не нужно.

Зачем это спрашивают: Сильный ответ отличает повторное связывание от мутации и объясняет, почему shadowing может менять тип.

types

К скалярным типам Rust относятся целые числа, числа с плавающей точкой, логические значения и символы.

  • Целые типы включают знаковые i8 до i128 и isize, а также беззнаковые u8 до u128 и usize.
  • Числа с плавающей точкой используют f32 или f64, а bool содержит true или false.
  • char является четырёхбайтовым скалярным значением Unicode, а не одним байтом UTF-8.

Зачем это спрашивают: Интервьюер оценивает знание базовых типов, включая важное различие между char и байтом.

types

usize является естественным типом для размеров памяти и индексов коллекций, потому что его ширина совпадает с шириной указателя целевой платформы.

  • Для индексации срезов и массивов требуется usize.
  • Тип фиксированной ширины, например u32, лучше подходит формату файла или протоколу, где нужно ровно 32 бита.
  • Преобразования между целыми типами должны быть явными, поскольку значение может не поместиться в целевой тип.

Зачем это спрашивают: Вопрос проверяет, связываете ли вы выбор целого типа с индексами, шириной платформы и внешними форматами данных.

types

Кортеж хранит фиксированное число значений, которые могут иметь разные типы, а массив хранит фиксированное число значений одного типа.

  • Элементы кортежа читаются по постоянным позициям вроде value.0 или через деструктуризацию.
  • Тип массива включает тип элемента и длину, например [u8; 16].
  • Массивы хранятся как inline-значения и могут заимствоваться как срезы для API с переменной длиной данных.

Зачем это спрашивают: Интервьюер проверяет понимание базовых составных типов Rust и их структуры на уровне типов.

basics

Statement выполняет действие без пригодного для использования значения, а expression вычисляется в значение.

  • Вызовы функций, блоки match и if, а также обычные блоки могут быть expressions.
  • Последний expression блока становится значением блока, если после него нет точки с запятой.
  • Добавление точки с запятой обычно превращает expression в statement со значением unit-типа ().

Зачем это спрашивают: Сильный ответ объясняет, почему функции и блоки Rust возвращают значения без явного ключевого слова return.

ownershipsystem-design

Владение позволяет Rust управлять памятью и предотвращать некорректный доступ без сборщика мусора.

  • У каждого значения есть владелец, который определяет момент удаления значения.
  • Присваивание и вызов функции могут передавать владение, после чего старый binding становится недоступен.
  • Заимствование даёт временный доступ без передачи владения.

Зачем это спрашивают: Интервьюер хочет увидеть ясную модель, связывающую владение с memory safety, очисткой ресурсов и заимствованием.

ownership

У каждого значения есть один владелец, одновременно владелец может быть только один, а при выходе владельца из области видимости значение удаляется.

  • Владение non-Copy значением может перейти от одного binding к другому.
  • Ссылка заимствует значение и не становится его владельцем.
  • Компилятор проверяет эти правила до запуска программы.

Зачем это спрашивают: Интервьюер проверяет, можете ли вы точно сформулировать модель владения, а не только назвать её защитой памяти.

При присваивании String владение обычно перемещается в новый binding.

  • String владеет данными в куче, поэтому копирование только его stack-метаданных между двумя владельцами создало бы риск двойного освобождения.
  • После move исходный binding использовать нельзя, если владение не было возвращено.
  • Метод clone создаёт независимый String, когда оба значения действительно нужны.

Зачем это спрашивают: Сильный ответ связывает move-семантику с выделением памяти String и объясняет недоступность исходного binding.

ownership

Передача аргумента следует тем же правилам, что и присваивание: non-Copy значения перемещаются, а Copy значения копируются.

  • Функция, принимающая String по значению, владеет им и может удалить его до возврата.
  • Параметр &str или &String заимствует данные, а не забирает значение у вызывающей стороны.
  • Возврат значения может передать владение обратно, но заимствование обычно яснее, если потребление не требуется.

Зачем это спрашивают: Интервьюер оценивает, можете ли вы предсказать передачу владения через простую границу функции.

traitsownership

Copy означает, что присваивание и передача аргумента неявно дублируют значение вместо его перемещения.

  • Простые скалярные типы и кортежи только из Copy-значений обычно реализуют Copy.
  • Тип с реализацией Drop не может реализовать Copy, поскольку неявное дублирование конфликтовало бы с уникальной очисткой.
  • Copy предназначен для дешёвого побитового дублирования и должен быть явно реализован или выведен для пользовательского типа.

Зачем это спрашивают: Интервьюер проверяет понимание семантического эффекта и важных ограничений Copy.

ownership

Clone выполняет явное дублирование через метод clone, а Copy разрешает неявное дублирование при присваивании и передаче аргумента.

  • Clone может выделять память или выполнять специальную логику, как при копировании буфера String.
  • Каждый Copy-тип обязан реализовывать Clone, но многие Clone-типы не являются Copy.
  • Вызов clone должен выражать реальную потребность во втором owned-значении, а не автоматически заглушать ошибку владения.

Зачем это спрашивают: Сильный ответ отличает явное потенциально дорогое дублирование от неявного дешёвого копирования.

Значение удаляется при выходе владельца из области видимости, если до этого владение не было перемещено.

  • Rust вызывает реализацию Drop, а затем освобождает принадлежащие значению ресурсы.
  • Локальные переменные обычно удаляются в порядке, обратном их созданию.
  • Функция std::mem::drop позволяет явно завершить владение до конца окружающей области.

Зачем это спрашивают: Интервьюер проверяет базовое понимание детерминированной очистки ресурсов и перемещённых значений.

ownership

Заимствование даёт временный доступ к значению через ссылку без передачи владения.

  • Общая ссылка имеет тип &T и позволяет читать заимствованное значение.
  • Изменяемая ссылка имеет тип &mut T и позволяет менять заимствованное значение.
  • Владелец остаётся ответственным за удаление значения после завершения всех заимствований.

Зачем это спрашивают: Интервьюер хочет увидеть прямое различие между временным доступом и передачей владения.

ownership

В один момент Rust разрешает либо любое число общих ссылок, либо одну изменяемую ссылку на одно значение.

  • Общие ссылки разрешают одновременное чтение, но не мутацию через эти ссылки.
  • Эксклюзивная изменяемая ссылка не даёт другим ссылкам наблюдать значение во время изменения.
  • Эти правила предотвращают гонки данных и инвалидированные ссылки при компиляции.

Зачем это спрашивают: Сильный ответ формулирует правило эксклюзивности и связывает его с обеспечиваемой безопасностью.

ownership

Мутация через &mut T разрешена, потому что ссылка даёт эксклюзивный изменяемый доступ к целевому значению.

  • Исходное значение должно быть объявлено с mut, прежде чем код сможет взять на него изменяемую ссылку.
  • Перепривязка переменной с изменяемой ссылкой отличается от мутации значения по этой ссылке.
  • Параметр функции value: &mut T может менять T без получения владения.

Зачем это спрашивают: Интервьюер проверяет, различаете ли вы изменяемую цель, изменяемую ссылку и изменяемый binding.

ownership

Rust отклоняет ссылку, которая может пережить своё значение, потому что её использование обратилось бы к недействительной памяти.

  • Локальное значение удаляется, когда его владелец покидает область видимости.
  • Возврат ссылки на такое значение оставил бы вызывающую сторону с указателем на освобождённое хранилище.
  • Обычное решение состоит в возврате owned-значения, владение которым корректно переходит к вызывающей стороне.

Зачем это спрашивают: Интервьюер оценивает, понимаете ли вы причину проверки lifetime с точки зрения безопасности памяти.

lifetimes

Lifetime является областью кода, в которой ссылка остаётся действительной.

  • Компилятор сравнивает lifetimes, чтобы заимствованное значение жило дольше каждого использования ссылки.
  • Большинство lifetimes выводятся и не требуют письменной аннотации.
  • Lifetime-аннотация описывает отношения между ссылками, но не продлевает жизнь значений.

Зачем это спрашивают: Сильный ответ определяет lifetimes как отношения действительности, а не runtime-таймеры или продление памяти.

lifetimes

Аннотация нужна, когда компилятору требуется знать, с каким входным заимствованием связан возвращаемый reference.

  • Сигнатура fn longest<'a>(x: &'a str, y: &'a str) -> &'a str указывает, что результат действителен в их общей доступной области.
  • Аннотация не требует, чтобы оба аргумента имели буквально одинаковые lifetimes.
  • Она не позволяет использовать возвращённую ссылку дольше, чем разрешает любой из возможных источников.

Зачем это спрашивают: Интервьюер проверяет, умеете ли вы читать простое lifetime-отношение в сигнатуре функции.

lifetimestypes

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