Mutable References
Мы можем исправить код из Listing 4-6, чтобы позволить нам изменять заимствованное значение, сделав всего несколько небольших изменений, которые используют, вместо этого, изменяемую ссылку:
Filename: src/main.rs
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
Во-первых, мы меняем s
на mut
. Затем мы создаем изменяемую ссылку с помощью &mut s
в месте, где мы вызываем функцию change
, и обновляем сигнатуру функции, чтобы она принимала изменяемую ссылку с some_string: &mut String
. Это делает очень ясно, что функция change
изменит значение, которое она заимствует.
Изменяемые ссылки имеют одну большую ограничительную особенность: если у вас есть изменяемая ссылка на значение, вы не можете иметь других ссылок на это значение. Этот код, который пытается создать две изменяемые ссылки на s
, будет ошибочным:
Filename: src/main.rs
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
println!("{r1}, {r2}");
Вот ошибка:
error[E0499]: cannot borrow `s` as mutable more than once at a time
--> src/main.rs:5:14
|
4 | let r1 = &mut s;
| ------ first mutable borrow occurs here
5 | let r2 = &mut s;
| ^^^^^^ second mutable borrow occurs here
6 |
7 | println!("{r1}, {r2}");
| -- first borrow later used here
Эта ошибка говорит, что этот код недопустим, потому что мы не можем заимствовать s
как изменяемое более одного раза одновременно. Первая изменяемая заимствование находится в r1
и должно длиться до тех пор, пока оно не будет использовано в println!
, но между созданием этой изменяемой ссылки и ее использованием мы пытались создать другую изменяемую ссылку в r2
, которая заимствует ту же самую информацию, что и r1
.
Ограничение, предотвращающее создание нескольких изменяемых ссылок на одну и ту же информацию одновременно, позволяет осуществлять изменение, но в очень контролируемом стиле. Это вызывает затруднения у новых пользователей Rust, потому что большинство языков позволяет изменять данные в любое время, когда это необходимо. Преимущество этого ограничения заключается в том, что Rust может предотвратить конфликты доступа к данным на этапе компиляции. Конфликт доступа к данным (data race) похож на ситуацию с гонкой и возникает, когда происходят следующие три действия:
- Два или более указателей обращаются к одной и той же информации одновременно.
- Хотя бы один из указателей используется для записи в данные.
- Нет механизма, который бы синхронизировал доступ к данным.
Конфликты доступа к данным вызывают неопределенное поведение и могут быть сложными для диагностики и исправления, когда вы пытаетесь их обнаружить во время выполнения программы; Rust предотвращает эту проблему, не компилируя код с конфликтами доступа к данным!
Как всегда, мы можем использовать фигурные скобки для создания новой области видимости, что позволяет иметь несколько изменяемых ссылок, но не одновременно:
let mut s = String::from("hello");
{
let r1 = &mut s;
} // r1 выходит из области видимости здесь, поэтому мы можем создать новую ссылку без проблем
let r2 = &mut s;
Rust налагает аналогичное правило при комбинации изменяемых и неизменяемых ссылок. Этот код вызывает ошибку:
let mut s = String::from("hello");
let r1 = &s; // нет проблем
let r2 = &s; // нет проблем
let r3 = &mut s; // ОШИБКА!
println!("{r1}, {r2}, and {r3}");
Вот ошибка:
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as
immutable
--> src/main.rs:6:14
|
4 | let r1 = &s; // no problem
| -- immutable borrow occurs here
5 | let r2 = &s; // no problem
6 | let r3 = &mut s; // ОШИБКА!
| ^^^^^^ mutable borrow occurs here
7 |
8 | println!("{r1}, {r2}, and {r3}");
| -- immutable borrow later used here
Уфф! Мы также не можем иметь изменяемую ссылку, если у нас есть неизменяемая ссылка на одно и то же значение.
Пользователи неизменяемой ссылки не ожидают, чтобы значение вдруг изменилось под их ногами! Однако несколько неизменяемых ссылок допустимы, потому что никто, кто только читает данные, не может повлиять на то, как другие читают эти данные.
Обратите внимание, что область видимости ссылки начинается от места, где она создается, и продолжается до последнего момента, когда эта ссылка используется. Например, этот код скомпилируется, потому что последнее использование неизменяемых ссылок, println!
, происходит до того, как появляется изменяемая ссылка:
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
println!("{r1} and {r2}");
// переменные r1 и r2 не будут использоваться после этой точки
let r3 = &mut s; // no problem
println!("{r3}");
Области видимости неизменяемых ссылок r1
и r2
заканчиваются после println!
, где они последний раз используются, что происходит до создания изменяемой ссылки r3
. Эти области видимости не перекрываются, поэтому этот код допустим: компилятор может понять, что ссылка больше не используется в точке до конца области видимости.
Даже если ошибки при заимствовании могут быть раздражающими в некоторых случаях, помните, что это компилятор Rust, который ранним образом (во время компиляции, а не во время выполнения) показывает потенциальную ошибку и точно указывает, где именно проблема. Затем вам не нужно искать причину того, почему ваши данные не такие, как вы думали.