lenec ru

← все посты

Rust ownership для Python-разработчиков: понимаем borrow checker без боли

16K

Если вы пришли в 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 проявились бы только в продакшене под нагрузкой.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.