Заимствование: & и &mut

Заимствование позволяет дать доступ к значению, не передавая владение: ссылки & и &mut и правила, которые их охраняют.

Заимствование (borrowing) — доступ к значению через ссылку (&) без передачи владения. Владелец остаётся прежним.

Зачем нужны ссылки

Постоянно перемещать значение в функцию и возвращать обратно утомительно. Чаще функции нужно просто посмотреть на данные, а не забрать их насовсем. Для этого есть ссылки: вы даёте функции «почитать книгу», не отдавая её в собственность.

fn length(s: &String) -> usize {  // принимаем ссылку, а не владение
    s.len()
} // s выходит из области видимости, но НИЧЕГО не удаляется — это лишь ссылка

fn main() {
    let text = String::from("привет");
    let n = length(&text);        // передаём ссылку через &
    println!("'{text}' содержит {n} байт"); // text всё ещё наш!
}

Вывод:

'привет' содержит 12 байт

Знак & создаёт ссылку — «одолжить, не владея». Функция получает доступ, но владелец остаётся в main. После вызова text по-прежнему доступен. (12 байт, а не 6: каждая кириллическая буква в UTF-8 занимает два байта.)

Изменяемые ссылки &mut

Обычная ссылка & даёт только чтение. Чтобы через ссылку изменить значение, нужна изменяемая ссылка &mut — и сама переменная должна быть объявлена как mut.

fn add_excl(s: &mut String) {
    s.push_str("!");      // меняем чужую строку через изменяемую ссылку
}

fn main() {
    let mut text = String::from("Эй");
    add_excl(&mut text);     // даём изменяемую ссылку
    println!("{text}");
}

Вывод:

Эй!

Правило заимствования — золотое правило Rust

За ссылками следит проверяющий заимствования (borrow checker). Правило одно, но фундаментальное. В любой момент для значения может существовать либо:

  • сколько угодно неизменяемых ссылок & (много читателей),
  • либо ровно одна изменяемая ссылка &mut (один писатель),

но не то и другое одновременно.

Аналогия: документ в Google Docs. Читать одновременно могут многие. Но если кто-то редактирует, остальным нельзя ни читать, ни редактировать — иначе они увидели бы несогласованное состояние. Это правило на корню убивает гонки данных: их просто невозможно выразить.

fn main() {
    let mut s = String::from("привет");

    let r1 = &s;          // ок: читатель
    let r2 = &s;          // ок: ещё читатель
    println!("{r1} {r2}"); // r1 и r2 больше не используются

    let w = &mut s;       // ок: теперь один писатель (читателей уже нет)
    w.push_str("!");
    println!("{w}");
}

Вывод:

привет привет
привет!

А вот так компилятор не пропустит — одновременно живут читатель и писатель:

fn main() {
    let mut s = String::from("привет");
    let r = &s;           // неизменяемая ссылка
    let w = &mut s;       // ОШИБКА: cannot borrow `s` as mutable
    println!("{r} {w}");  //         because it is also borrowed as immutable
}

Висячих ссылок не бывает

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

Итог

  • &value — заимствование для чтения; &mut value — для изменения (переменная должна быть mut).
  • Правило: либо много &, либо одна &mut — но не одновременно.
  • Эти правила на этапе компиляции исключают гонки данных и висячие ссылки.
Проверьте себя
1. Что делает оператор & перед значением?
AПеремещает владение
BСоздаёт ссылку — заимствует значение без передачи владения
CКопирует значение
DУдаляет значение
2. Сколько изменяемых ссылок &mut на одно значение может существовать одновременно?
AСколько угодно
BРовно одна, и при этом не должно быть неизменяемых ссылок
CНе больше трёх
DНи одной
3. Какой класс ошибок предотвращает правило заимствования?
AСинтаксические ошибки
BГонки данных и одновременное чтение несогласованного состояния
CОшибки округления
DОпечатки в именах
Поддержать проект