Топ ошибок начинающих Rust разработчиков: от А до Я

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

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

Начать курс

Топ ошибок начинающих Rust разработчиков: от А до Я

Rust — мощный и безопасный язык программирования, но его строгая система владения и заимствования часто ставит в тупик новичков. В этой статье мы разберём самые распространённые ошибки, с которыми сталкиваются начинающие разработчики на Rust. Каждая ошибка сопровождается примером кода, объяснением и правильным решением. Поехали!

1. Игнорирование системы владения (Ownership)

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

❌ Ошибочный код:

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

✅ Правильное решение:

fn main() {
    let s1 = String::from("привет");
    let s2 = s1.clone(); // Явное копирование
    println!("{}", s1); // Теперь работает
    println!("{}", s2);
}

Совет: Используйте .clone() только когда это действительно нужно. Для примитивных типов (i32, bool) копирование происходит автоматически.

2. Путаница между String и &str

Новички часто не понимают разницу между владеемой строкой String и строковым срезом &str. Это приводит к ошибкам при передаче строк в функции.

❌ Ошибочный код:

fn print_message(msg: String) {
    println!("{}", msg);
}

fn main() {
    let greeting = "Здравствуйте"; // &str
    print_message(greeting); // Ошибка! Ожидается String
}

✅ Правильное решение:

fn print_message(msg: &str) {
    println!("{}", msg);
}

fn main() {
    let greeting = "Здравствуйте";
    print_message(greeting); // Работает!
    
    let owned = String::from("Мир");
    print_message(&owned); // &String автоматически преобразуется в &str
}

Золотое правило: В параметрах функций используйте &str, если вам не нужно передавать владение строкой.

3. Нарушение правил заимствования (Borrowing)

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

❌ Ошибочный код:

fn main() {
    let mut data = vec![1, 2, 3];
    let ref1 = &data;
    let ref2 = &mut data; // Ошибка! Уже есть неизменяемая ссылка
    println!("{}", ref1);
}

✅ Правильное решение:

fn main() {
    let mut data = vec![1, 2, 3];
    {
        let ref1 = &data;
        println!("{}", ref1); // ref1 живёт только в этом блоке
    } // ref1 выходит из области видимости
    let ref2 = &mut data; // Теперь можно
    ref2.push(4);
    println!("{:?}", ref2);
}

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

4. Проблемы с временами жизни (Lifetimes)

Ошибки, связанные с аннотациями времён жизни, пугают новичков больше всего. Самая типичная — возврат ссылки на локальную переменную.

❌ Ошибочный код:

fn get_reference() -> &str {
    let local = String::from("тест");
    &local // Ошибка! local будет уничтожена после выхода из функции
}

✅ Правильное решение (через владение):

fn get_reference() -> String {
    let local = String::from("тест");
    local // Возвращаем владение
}

fn main() {
    let s = get_reference();
    println!("{}", s);
}

✅ Правильное решение (через параметр):

fn get_reference<'a>(input: &'a str) -> &'a str {
    input // Возвращаем ссылку с тем же временем жизни
}

fn main() {
    let data = String::from("тест");
    let result = get_reference(&data);
    println!("{}", result);
}

Запоминалка: Если функция возвращает ссылку, она должна получить её из аргументов. Нельзя создать ссылку внутри функции и вернуть её.

5. Итерация и заимствование коллекций

При итерации по коллекции с последующей модификацией новички часто натыкаются на borrow checker.

❌ Ошибочный код:

fn main() {
    let mut numbers = vec![1, 2, 3, 4, 5];
    for num in &numbers {
        if *num > 3 {
            numbers.push(*num + 1); // Ошибка! Нельзя изменять коллекцию во время итерации
        }
    }
}

✅ Правильное решение (сбор результатов):

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    let mut new_numbers: Vec = Vec::new();
    
    for &num in &numbers {
        if num > 3 {
            new_numbers.push(num + 1);
        }
    }
    
    println!("{:?}", new_numbers);
}

✅ Альтернатива через итераторы:

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    let result: Vec = numbers
        .iter()
        .filter(|&&x| x > 3)
        .map(|&x| x + 1)
        .collect();
    
    println!("{:?}", result);
}

Совет: Изучите методы итераторов (map, filter, collect) — они часто избавляют от проблем с заимствованием.

6. Игнорирование Result и Option

Многие новички пытаются игнорировать ошибки, используя unwrap() везде, где только можно.

❌ Ошибочный код:

fn main() {
    let result = "42".parse::<i32>().unwrap(); // Паника при ошибке
    let bad = "abc".parse::<i32>().unwrap(); // Программа упадёт
    println!("{}", bad);
}

Блоги

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