Linux networking: ss, ip и nftables — диагностика сети за 5 минут
Если вы пришли в Rust из Python, borrow checker — первая стена. В Python память управляется GC, и вы не думаете о владении. В Rust ownership — центральная концепция, гарантирующая безопасность памяти без сборщика мусора. Разберём через призму Python-мышления.
Зачем ownership: memory safety без GC
Python использует reference counting + циклический GC. Удобно, но дорого: непредсказуемые паузы, overhead на каждый объект, GIL из-за невозможности гарантировать отсутствие data races.
Rust выбрал другой путь: компилятор статически проверяет, что у данных ровно один владелец. Когда владелец выходит из scope — память освобождается. Нет GC, нет runtime-overhead, нет data races. Цена — вы должны убедить компилятор, что код безопасен.
Аналогии с Python: ярлыки vs владение
В Python переменные — ярлыки, привязанные к объектам. Несколько ярлыков указывают на один объект:
# Python: a и b — ярлыки на один список
a = [1, 2, 3]
b = a
b.append(4)
print(a) # [1, 2, 3, 4] — мутация видна через оба имени
В Rust переменная — владелец значения. Присваивание передаёт владение (move):
fn main() {
let a = vec![1, 2, 3];
let b = a; // ownership перешёл к b
// println!("{:?}", a); // ОШИБКА: value used after move
println!("{:?}", b); // [1, 2, 3]
}
В Python объект живёт, пока есть хоть один ярлык. В Rust значение живёт, пока жив единственный владелец.
Move, Clone, Copy — когда что происходит
Move — передача владения. По умолчанию для типов в куче (String, Vec, Box):
let s1 = String::from("hello");
let s2 = s1; // move: s1 недействителен
Clone — явное глубокое копирование:
let s1 = String::from("hello");
let s2 = s1.clone(); // оба действительны
println!("{} {}", s1, s2);
Copy — автоматическое побитовое копирование для простых типов на стеке (i32, f64, bool):
let x: i32 = 42;
let y = x; // Copy: x по-прежнему действителен
Правило: тип реализует Copy — присваивание копирует. Иначе — перемещает.
References и borrowing: &T vs &mut T
Чтобы не передавать владение, Rust позволяет «одолжить» значение:
fn print_len(s: &String) { // иммутабельное заимствование
println!("len: {}", s.len());
}
fn add_exclaim(s: &mut String) { // мутабельное заимствование
s.push('!');
}
fn main() {
let mut greeting = String::from("Hello");
print_len(&greeting);
add_exclaim(&mut greeting);
println!("{}", greeting); // Hello!
}
Правила borrowing:
- Сколько угодно иммутабельных ссылок
&Tодновременно - Ровно одна мутабельная ссылка
&mut T - Нельзя
&Tи&mut Tодновременно
Аналогия: &T — read lock, &mut T — write lock. Rust проверяет это при компиляции, не в рантайме.
Lifetime annotations: когда компилятор не справляется
Обычно lifetimes выводятся автоматически. Но когда функция возвращает ссылку, нужна подсказка:
// Результат живёт столько же, сколько кратчайший из входов
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() >= b.len() { a } else { b }
}
fn main() {
let s1 = String::from("long string");
{
let s2 = String::from("hi");
let result = longest(&s1, &s2);
println!("{}", result); // OK: s2 ещё жив
}
// result здесь недоступен — s2 уничтожен
}
Lifetime 'a — не время жизни, а ограничение: «результат не переживёт ни один из входов». Компилятор предотвращает dangling references.
Типичные ошибки новичков
«value used after move» — использовали переменную после передачи владения. Решение: .clone() или передавайте ссылку.
«cannot borrow as mutable because it is also borrowed as immutable» — одновременно &T и &mut T. Решение: завершите использование иммутабельной ссылки до мутабельного заимствования.
«does not live long enough» — ссылка переживает данные. Решение: измените scope или верните owned-значение.
«cannot move out of borrowed content» — забираете владение через ссылку. Решение: .clone() или перестройте логику.
Главный совет: читайте ошибки компилятора целиком. Rust выдаёт подробные объяснения с подсказками. Через 2-3 недели borrow checker перестаёт быть врагом — он ловит баги, которые в Python проявились бы только в продакшене под нагрузкой.