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

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

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

Начать курс

Введение: почему Rust кажется сложным, но стоит того

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

1. Борьба с заимствованием (Borrow Checker)

Самая известная "боль" новичков — это система владения (ownership) и заимствования (borrowing). Компилятор не даёт вам просто так создать ссылку на данные, если они могут быть изменены или удалены.

1.1. Попытка изменять данные через неизменяемую ссылку

Одна из первых ошибок — попытка изменить переменную, на которую уже есть неизменяемая ссылка (&).

let mut x = 5;
let y = &x;   // неизменяемая ссылка
let z = &mut x; // ОШИБКА: нельзя создать изменяемую ссылку, пока есть неизменяемая
println!("{}", y);

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

let mut x = 5;
{
    let y = &x;   // область видимости y ограничена
    println!("{}", y);
}
let z = &mut x;   // теперь можно

1.2. Возврат ссылки на локальную переменную

Новички часто пытаются вернуть ссылку на данные, созданные внутри функции. Это приводит к ошибке "dangling reference".

fn get_ref() -> &String {
    let s = String::from("hello");
    &s // ОШИБКА: s будет удалена после выхода из функции
}

Как исправить: Возвращайте владение (саму строку) или используйте статическое время жизни ('static), если данные живут вечно.

fn get_string() -> String {
    let s = String::from("hello");
    s // передаём владение
}

2. Неправильное использование lifetimes (времени жизни)

Когда вы начинаете работать с функциями, принимающими несколько ссылок, компилятор требует указать, как они соотносятся по времени жизни. Новички либо игнорируют lifetimes, либо пишут 'static везде, где это не нужно.

2.1. Пропуск аннотаций lifetimes в сигнатурах

Если функция принимает две ссылки и возвращает одну, Rust не может сам определить, какая из них "живёт" дольше.

fn longest(x: &str, y: &str) -> &str { // ОШИБКА: нужен lifetime
    if x.len() > y.len() { x } else { y }
}

Как исправить: Добавьте общий параметр времени жизни.

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

2.2. Излишнее использование 'static

'static означает, что ссылка живёт всю программу. Использовать его для строковых литералов — ок, но для динамических данных — плохая практика.

fn get_static() -> &'static str {
    "hello" // правильно
}

fn get_dynamic() -> &'static str {
    let s = String::from("hello");
    &s // ОШИБКА: s не живёт вечно
}

Совет: Старайтесь не использовать 'static без необходимости. Лучше передавайте владение или используйте Arc<Mutex<T>> для многопоточности.

3. Злоупотребление unwrap() и паника

Rust поощряет явную обработку ошибок через Result и Option. Но новички часто используют .unwrap() или .expect() везде, чтобы "заткнуть" компилятор. Это приводит к панике в рантайме.

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

let v = vec![1, 2, 3];
let first = v.get(10).unwrap(); // паника, если индекс вне диапазона

Как исправить: Используйте match, if let или оператор ? для проброса ошибки.

fn get_element(vec: &Vec<i32>, index: usize) -> Option<&i32> {
    vec.get(index)
}

fn main() {
    let v = vec![1, 2, 3];
    match v.get(10) {
        Some(val) => println!("{}", val),
        None => println!("Индекс вне диапазона"),
    }
}

3.2. Паника в библиотечном коде

Если вы пишете библиотеку, паника делает её ненадёжной. Пользователь не ожидает, что ваш код может упасть. Всегда возвращайте Result.

// Плохо
fn divide(a: i32, b: i32) -> i32 {
    if b == 0 {
        panic!("Деление на ноль");
    }
    a / b
}

// Хорошо
fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        return Err("Деление на ноль".to_string());
    }
    Ok(a / b)
}

4. Ошибки с владением в коллекциях и итераторах

Когда вы работаете с векторами, хеш-картами или итераторами, легко запутаться, кто владеет данными.

4.1. Заимствование при итерации с изменением

Попытка изменить вектор во время итерации по нему — классика.

let mut v = vec![1, 2, 3];
for i in &v {        // неизменяемая ссылка
    v.push(4);        // ОШИБКА: изменяемое заимствование                

Блоги

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