Введение: почему 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); // ОШИБКА: изменяемое заимствование