Как всё началось
Я писала на Python три года. Удобный, тёплый, ламповый. Потом пришёл Rust. Первая неделя была натуральной пыткой: компилятор ругался на каждую вторую строчку, причём на вещи, которые в Python работали бы просто отлично.
Самым болезненным был вот такой сценарий. Я создаю строку, передаю её в функцию, и потом пытаюсь использовать снова — и компилятор говорит мне, что строка «moved» и я больше не могу к ней обращаться. Как?! Почему?! Что ты от меня хочешь??
fn main() {
let name = String::from("Молли");
greet(name); // name перемещается в функцию
println!("Привет, {}!", name); // ошибка: use of moved value
}
fn greet(name: String) {
println!("Добрый день, {}!", name);
}
Я понимала, что происходит технически — функция забирает владение строкой. Но не понимала, зачем это нужно, и это делало ситуацию невыносимой.
Ментальный сдвиг: думать о памяти как о ресурсе
Переломный момент наступил, когда я начала думать о переменных не как о «коробочках с данными», а как о владельцах ресурса. В каждый момент времени ресурсом владеет ровно один владелец. Когда владелец умирает — ресурс освобождается автоматически.
Для меня помогла аналогия с кормлением кота. Миска с едой — это ресурс. Если я передаю миску другому коту, у меня её больше нет. Это не баг — это здравый смысл.
fn main() {
let name = String::from("Молли");
greet(&name); // передаём ссылку, не владение
println!("Привет, {}!", name); // теперь работает!
}
fn greet(name: &str) { // принимаем ссылку
println!("Добрый день, {}!", name);
}
Три правила, которые всё объясняют
Я записала их на стикер и повесила над монитором:
- У каждого значения есть ровно один владелец.
- Можно иметь сколько угодно неизменяемых ссылок или одну изменяемую — но не то и другое одновременно.
- Ссылка не может пережить владельца.
Пример из реальной жизни
Вот упрощённая версия кода из моего проекта catsh. Функция, которая
читает конфиг и при необходимости его модифицирует:
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-код стал:
- Чище — явные ссылки делают намерения очевидными.
- Надёжнее — ни одного use-after-free, ни одной гонки данных.
- Быстрее — нет GC, нет подсчёта ссылок, только стек и куча.
Rust не сложнее других языков. Он просто заставляет вас думать о вещах, которые другие языки молча делали за вас — иногда неправильно.
Итог
Если вы только начинаете с Rust и borrow checker кажется врагом — держитесь. Это инвестиция. Через месяц вы начнёте благодарить его за каждую пойманную ошибку.
А если совсем туго — напишите мне. У меня ещё есть стикеры с тремя правилами 🐾