Топ ошибок начинающих 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);
}