Как пройти собеседование по Rust: топ вопросов

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: Почему Rust так требователен на собеседованиях?

Rust — это язык системного программирования, который славится своей безопасностью памяти и высокой производительностью. Компилятор Rust строг, но справедлив: он не пропустит ошибку владения (ownership) или висячую ссылку (dangling reference). Именно поэтому на собеседованиях по Rust редко спрашивают синтаксис "по бумажке". Вместо этого интервьюеры проверяют глубокое понимание модели памяти, управления ресурсами и идиоматичного кода.

В этой статье мы разберем топ-10 вопросов, которые чаще всего задают на собеседованиях для Junior и Middle Rust-разработчиков. Каждый вопрос сопровождается пояснением и примером кода. Если вы готовитесь к интервью, эта статья станет вашим чек-листом.

1. Владение (Ownership) и заимствование (Borrowing)

Это фундамент Rust. Без понимания владения вы не пройдете собеседование. Интервьюер может попросить объяснить разницу между String и &str или спросить, почему код не компилируется.

Вопрос: Объясните, что такое владение и как работает передача данных в функции.

Ответ: В Rust у каждого значения есть ровно один владелец. Когда переменная выходит из области видимости, значение удаляется (drop). Передача в функцию означает перемещение (move) владения, если тип не реализует трейт Copy.

fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // s1 перемещено в s2
    // println!("{}", s1); // Ошибка! s1 больше не владеет строкой
    println!("{}", s2);
}

Если вы хотите передать данные без потери владения, используйте ссылки (&):

fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1); // заимствование
    println!("Длина '{}' равна {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

Вопрос: Что такое мутабельное заимствование и почему может быть только одно?

Ответ: Мутабельное заимствование (&mut) позволяет изменить значение через ссылку. Компилятор гарантирует, что в области видимости существует только одна мутабельная ссылка, чтобы предотвратить гонки данных (data races).

fn main() {
    let mut s = String::from("hello");
    let r1 = &mut s;
    // let r2 = &mut s; // Ошибка! Нельзя две мутабельные ссылки
    r1.push_str(", world");
    println!("{}", r1);
}

2. Времена жизни (Lifetimes)

Времена жизни — это то, что пугает новичков. На собеседовании вас попросят объяснить, зачем нужны аннотации 'a и как компилятор выводит lifetimes.

Вопрос: Когда нужно явно указывать времена жизни?

Ответ: Когда функция возвращает ссылку, и компилятор не может самостоятельно определить, как долго она будет жить. Классический пример — функция, возвращающая самую длинную строку из двух:

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

fn main() {
    let string1 = String::from("длинная строка");
    let result;
    {
        let string2 = String::from("короткая");
        result = longest(&string1, &string2);
    }
    // println!("{}", result); // Ошибка! string2 удалена, ссылка висячая
}

Аннотация 'a говорит: "время жизни возвращаемой ссылки равно меньшему из времен жизни входных ссылок".

Вопрос: Что такое elision (правила пропуска lifetimes)?

Ответ: Компилятор автоматически вставляет lifetimes в простых случаях. Если функция принимает одну ссылку и возвращает ссылку — lifetime выводится. Если несколько входных ссылок — нужно указывать явно.

3. Enum, Pattern Matching и Option/Result

Rust активно использует алгебраические типы данных. Интервьюер проверит, как вы работаете с Option и Result, и понимаете ли мощь match.

Вопрос: В чем разница между Option и Result? Приведите примеры использования.

Ответ: Option<T> означает, что значение может быть (Some(T)) или отсутствовать (None). Result<T, E> означает, что операция может завершиться успешно (Ok(T)) или с ошибкой (Err(E)).

// Option: поиск элемента в векторе
fn find_item(items: &[i32], target: i32) -> Option<usize> {
    items.iter().position(|&x| x == target)
}

// Result: деление с проверкой на ноль
fn divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err(String::from("Деление на ноль"))
    } else {
        Ok(a / b)
    }
}

fn main() {
    match divide(10.0, 2.0) {
        Ok(result) => println!("Результат: {}", result),
        Err(e) => println!("Ошибка: {}", e),
    }
}

Вопрос: Как работает оператор ? (вопросительный знак)?

Ответ: Оператор ? — это синтаксический сахар для раннего возврата ошибки. Если значение Err, функция немедленно возвращает его. Если Ok — извлекает значение.

use std::fs::File;
use std::io::Read;

fn read_file(path: &str) -> Result<String, std::io::Error> {
    let mut file = File::open(path)?; // если ошибка — выход
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    Ok(contents)
}

4. Управление памятью: Stack, Heap и Box

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

Вопрос: Когда используется Box<T>? Чем

Блоги

Рекомендаций по книгам