сеньорчикОткрыть в Telegram
← все вопросывопросы для собеседований · Владение и лайфтаймы

Вопросы по владению и лайфтаймам Rust на собеседовании

Владение — входной фильтр Rust-собеса: тут отсеивают тех, кто читал книгу, но не спорил с borrow checker. На трёх правилах разговор не останавливается: дальше идут частичный move, NLL и вопрос, почему два узла, ссылающиеся друг на друга через Rc, не освобождаются никогда.

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

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

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

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

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

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

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

  1. #rs_borrowing1 / 9
    Сколько ссылок на одно значение допускает borrow checker одновременно?
    A)Одну & и одну &mut: чтение и запись не мешают друг другу
    B)Либо сколько угодно &, либо ровно одна &mut
    C)По одной ссылке каждого вида на область видимости
    D)Сколько угодно ссылок обоих видов: проверки для потоков
    показать ответ и разбор
    +B)Либо сколько угодно &, либо ровно одна &mut

    // разбор: Правило XOR: разделяемое чтение или эксклюзивная запись, но не вместе. Оно исключает целый класс багов — изменение коллекции во время итерации, висячие указатели после перевыделения буфера, гонки данных. Проверка статическая: нарушение не доходит до рантайма, компилятор роняет сборку с E0499 или E0502.

  2. #rs_drop_raii2 / 9
    Когда вызывается деструктор значения в Rust?
    A)Когда сборщик мусора решит, что значение недостижимо
    B)Когда владелец выходит из области видимости или перезаписывается
    C)В конце функции, где значение было создано, независимо от владения
    D)Только при явном вызове drop — иначе память освободит ОС
    показать ответ и разбор
    +B)Когда владелец выходит из области видимости или перезаписывается

    // разбор: Это RAII: освобождение привязано к владению. Область видимости кончилась — компилятор вставил вызов drop; значение перемещено — освобождать будет новый владелец; переменной присвоили новое значение — старое уничтожается. Никакого сборщика мусора и никаких пауз: моменты освобождения известны на этапе компиляции.

  3. #rs_interior_mut3 / 9
    Что такое внутренняя изменяемость (interior mutability)?
    A)Изменение значения через общую ссылку &T под контролем типа-обёртки
    B)Возможность объявить поле структуры mut отдельно от самой структуры
    C)Автоматическое превращение & в &mut, когда других ссылок нет
    D)Изменение данных из другого потока без синхронизации
    показать ответ и разбор
    +A)Изменение значения через общую ссылку &T под контролем типа-обёртки

    // разбор: Обычно &T запрещает запись. Cell, RefCell, Mutex и атомики дают легальный обход: снаружи ссылка общая, а обёртка сама следит, чтобы одновременных изменений не случилось — по значению (Cell), по счётчикам в рантайме (RefCell) или блокировкой (Mutex). Отдельного модификатора mut у полей в Rust нет.

  4. #rs_lifetimes4 / 9
    Что делает аннотация лайфтайма 'a в сигнатуре функции?
    A)Продлевает жизнь значения до конца программы
    B)Заставляет компилятор освободить память сразу после вызова
    C)Указывает, сколько тактов значение остаётся в кэше
    D)Описывает связь между временами жизни аргументов и результата
    показать ответ и разбор
    +D)Описывает связь между временами жизни аргументов и результата

    // разбор: Лайфтайм — это не рычаг управления памятью, а описание отношений: «результат живёт не дольше, чем вот этот аргумент». Компилятор проверяет, что вызывающий код это отношение соблюдает. На сгенерированный машинный код аннотации не влияют — они стираются после проверки.

  5. #rs_ownership_rules5 / 9
    Что происходит с переменной s: String после вызова take(s), где fn take(v: String)?
    A)s копируется в аргумент, оригинал остаётся доступным
    B)Передаётся ссылка, s остаётся владельцем буфера
    C)Владение уезжает в функцию, дальше s использовать не дадут
    D)s обнуляется в рантайме и становится пустой строкой
    показать ответ и разбор
    +C)Владение уезжает в функцию, дальше s использовать не дадут

    // разбор: Аргумент типа String принимается по значению — владение переходит вызываемой функции, и она отвечает за освобождение буфера. Компилятор помечает s как перемещённую: любое обращение после вызова — ошибка E0382. Если оригинал нужен дальше, передают ссылку (&s) или явную копию (s.clone()).

  6. #rs_smart_pointers6 / 9
    Зачем нужен Box<T>, если значение и так можно положить в переменную?
    A)Он делает значение неизменяемым и разделяемым между потоками
    B)Он считает ссылки и освобождает память, когда их не осталось
    C)Кладёт значение в кучу: нужен рекурсии и dyn Trait
    D)Он ускоряет доступ: куча выделяется быстрее стека
    показать ответ и разбор
    +C)Кладёт значение в кучу: нужен рекурсии и dyn Trait

    // разбор: Box — владеющий указатель на кучу с нулевым оверхедом сверх самого указателя. Он нужен там, где размер на этапе компиляции неизвестен или бесконечен: рекурсивный enum вроде списка, трейт-объект Box<dyn Error>, большое значение, которое дорого двигать по стеку. Освобождение — по выходу владельца из области видимости.

  7. #rs_borrowing7 / 9
    Почему этот код не собирается?
    let mut v = vec![1, 2, 3];
    let first = &v[0];
    v.push(4);
    println!("{}", first);
    A)Вектор запрещено менять после того, как его прочитали
    B)first указывает на копию, а печатать копию после push запрещено
    C)Ошибка в println: ссылку нужно разыменовать через *first
    D)push требует &mut, пока жива ссылка first — E0502
    показать ответ и разбор
    +D)push требует &mut, пока жива ссылка first — E0502

    // разбор: first — неизменяемое заимствование вектора, а push берёт &mut. Это прямое нарушение XOR-правила, и запрет не формальность: push может перевыделить буфер, и старый указатель повис бы. Уберите последний println — заимствование закончится раньше push, и код соберётся.

  8. #rs_drop_raii8 / 9
    Что напечатает код?
    struct D(&'static str);
    impl Drop for D {
        fn drop(&mut self) {
            println!("drop {}", self.0);
        }
    }
    let _a = D("a");
    let _b = D("b");
    println!("конец");
    A)конец, drop b, drop a
    B)drop a, drop b, конец
    C)конец, drop a, drop b
    D)конец — деструкторы для переменных с подчёркиванием не зовутся
    показать ответ и разбор
    +A)конец, drop b, drop a

    // разбор: Локальные переменные уничтожаются в порядке, обратном объявлению: стек разбирается сверху вниз, поэтому сначала b, потом a. Подчёркивание в начале имени лишь глушит предупреждение о неиспользовании. А вот голое имя _ — другое дело: оно вообще не создаёт привязку, и значение уничтожается сразу.

  9. #rs_interior_mut9 / 9
    Когда берут Cell<T>, а когда RefCell<T>?
    A)Cell — для чтения, RefCell — для записи
    B)Cell — для Copy-значений целиком, RefCell — когда нужны ссылки внутрь
    C)Cell — для примитивов, RefCell — для типов из стандартной библиотеки
    D)Cell — для однопоточного кода, RefCell — для многопоточного
    показать ответ и разбор
    +B)Cell — для Copy-значений целиком, RefCell — когда нужны ссылки внутрь

    // разбор: Cell работает только целыми значениями: get копирует, set заменяет — ссылок внутрь он не выдаёт, поэтому и проверять нечего, накладных расходов ноль. RefCell выдаёт настоящие ссылки (borrow, borrow_mut) и потому ведёт счётчики заимствований в рантайме, платя проверками и риском паники.

это 9 из 72

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

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

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