Заимствование: & и &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— но не одновременно. - Эти правила на этапе компиляции исключают гонки данных и висячие ссылки.