Почему я перестала бояться borrow checker — и начала его любить

Полгода назад я смотрела на ошибки компилятора Rust как на личное оскорбление. Сегодня расскажу, какой ментальный сдвиг помог мне наконец понять владение памятью — и почему это сделало мой код в три раза надёжнее.

Как всё началось

Я писала на Python три года. Удобный, тёплый, ламповый. Потом пришёл Rust. Первая неделя была натуральной пыткой: компилятор ругался на каждую вторую строчку, причём на вещи, которые в Python работали бы просто отлично.

Самым болезненным был вот такой сценарий. Я создаю строку, передаю её в функцию, и потом пытаюсь использовать снова — и компилятор говорит мне, что строка «moved» и я больше не могу к ней обращаться. Как?! Почему?! Что ты от меня хочешь??

src/main.rs
fn main() {
    let name = String::from("Молли");
    greet(name);             // name перемещается в функцию
    println!("Привет, {}!", name); // ошибка: use of moved value
}

fn greet(name: String) {
    println!("Добрый день, {}!", name);
}

Я понимала, что происходит технически — функция забирает владение строкой. Но не понимала, зачем это нужно, и это делало ситуацию невыносимой.

Ментальный сдвиг: думать о памяти как о ресурсе

Переломный момент наступил, когда я начала думать о переменных не как о «коробочках с данными», а как о владельцах ресурса. В каждый момент времени ресурсом владеет ровно один владелец. Когда владелец умирает — ресурс освобождается автоматически.

Для меня помогла аналогия с кормлением кота. Миска с едой — это ресурс. Если я передаю миску другому коту, у меня её больше нет. Это не баг — это здравый смысл.

src/main.rs
fn main() {
    let name = String::from("Молли");
    greet(&name);              // передаём ссылку, не владение
    println!("Привет, {}!", name); // теперь работает!
}

fn greet(name: &str) {        // принимаем ссылку
    println!("Добрый день, {}!", name);
}

Три правила, которые всё объясняют

Я записала их на стикер и повесила над монитором:

  1. У каждого значения есть ровно один владелец.
  2. Можно иметь сколько угодно неизменяемых ссылок или одну изменяемую — но не то и другое одновременно.
  3. Ссылка не может пережить владельца.
💡 Главный инсайт
Borrow checker не мешает вам. Он показывает вам баги ещё до запуска программы. Каждая «ошибка компилятора» — это гонка данных или use-after-free, которую вы только что избежали.

Пример из реальной жизни

Вот упрощённая версия кода из моего проекта catsh. Функция, которая читает конфиг и при необходимости его модифицирует:

src/config.rs
pub struct Config {
    pub aliases: Vec<(String, String)>,
    pub prompt:  String,
}

// Только читаем — неизменяемая ссылка
pub fn display(cfg: &Config) {
    for (alias, cmd) in &cfg.aliases {
        println!("{} → {}", alias, cmd);
    }
}

// Модифицируем — изменяемая ссылка
pub fn add_alias(cfg: &mut Config, alias: String, cmd: String) {
    cfg.aliases.push((alias, cmd));
}

// Только одна &mut ссылка в каждый момент времени —
// компилятор не даст вам случайно изменить конфиг из двух мест сразу

Что изменилось в моём коде

После того как я принял эту модель, мой Rust-код стал:

Rust не сложнее других языков. Он просто заставляет вас думать о вещах, которые другие языки молча делали за вас — иногда неправильно.

Итог

Если вы только начинаете с Rust и borrow checker кажется врагом — держитесь. Это инвестиция. Через месяц вы начнёте благодарить его за каждую пойманную ошибку.

А если совсем туго — напишите мне. У меня ещё есть стикеры с тремя правилами 🐾

Была ли статья полезной?