Листинг 4-5: возврат права владения на параметры
Но это слишком высокопарно и многословно для концепции, которая должна быть общей. К счастью для нас, в Rust есть возможность использовать значение без передачи права владения, называемая ссылками.
fn main
() { let s1 =
String
::from(
"hello"
); let
(s2, len) = calculate_length(s1); println!
(
"The length of '{}' is {}."
, s2, len);
} fn calculate_length
(s:
String
) -> (
String
, usize
) { let length = s.len();
// len() returns the length of a String
(s, length)
}
Ссылочные переменные и заимствование
Проблема с кодом кортежа в листинге 4-5 заключается в том, что мы должны вернуть
String из вызванной функции, чтобы использовать
String после вызова calculate_length
, потому что
String была перемещена в calculate_length
. Вместо этого мы можем предоставить ссылку на значение
String
. Ссылка похожа на указатель в том смысле, что это адрес, по которому мы можем проследовать, чтобы получить доступ к данным, хранящимся по этому адресу; эти данные принадлежат какой-то другой переменной. В отличие от указателя, ссылка гарантированно указывает на допустимое значение определённого типа в течение всего срока существования этой ссылки.
Вот как вы могли бы определить и использовать функцию calculate_length
, имеющую ссылку на объект в качестве параметра, вместо того, чтобы брать на себя ответственность за значение:
Файл: src/main.rs
Во-первых, обратите внимание, что весь код кортежа в объявлении переменной и возвращаемое значение функции исчезли. Во-вторых, обратите внимание, что мы передаём
&s1
в calculate_length и в его определении берём
&String а не
String
. Эти амперсанды представляют собой ссылки, и они позволяют вам ссылаться на некоторое значение, не принимая владение им. Рисунок 4-5 изображает эту концепцию.
fn main
() { let s1 =
String
::from(
"hello"
); let len = calculate_length(&s1); println!
(
"The length of '{}' is {}."
, s1, len);
} fn calculate_length
(s: &
String
) -> usize
{ s.len()
}
s
name value ptr s1
name value ptr len
5
capacity
5
index value
0
h
1
e
2
l
3
l
4
o
Рисунок 4-5: диаграмма для
&String s
, указывающей на
String s1
Примечание: противоположностью ссылки с использованием
&
является
разыменование, выполняемое с помощью оператора разыменования
*
. Мы увидим некоторые варианты использования оператора разыменования в главе 8 и обсудим детали разыменования в главе 15.
Давайте подробнее рассмотрим механизм вызова функции:
&s1
позволяет нам создать ссылку, которая ссылается на значение s1
, но не владеет им.
Поскольку она не владеет им, значение, на которое она указывает, не будет удалено,
когда ссылка перестанет использоваться.
Точно так же сигнатура функции использует
&
для указания на то, что тип параметра s
является ссылкой. Добавим несколько поясняющих аннотаций:
Область действия s
такая же, как и область действия любого параметра функции, но значение, на которое указывает ссылка, не удаляется, когда s
перестаёт использоваться,
потому что s
не является его владельцем. Когда функции имеют ссылки в качестве параметров вместо фактических значений, нам не нужно возвращать значения, чтобы вернуть право владения, потому что мы никогда не владели ими.
let s1 =
String
::from(
"hello"
); let len = calculate_length(&s1); fn calculate_length
(s: &
String
) -> usize
{
// s is a reference to a String s.len()
}
// Here, s goes out of scope. But because it does not have ownership of what
// it refers to, it is not dropped.
Мы называем процесс создания ссылки заимствованием. Как и в реальной жизни, если человек чем-то владеет, вы можете это у него позаимствовать. Когда вы закончите, вы должны вернуть это законному владельцу.
Так что же произойдёт, если мы попытаемся изменить что-то, что мы заимствуем?
Попробуйте запустить код из листинга 4-6. Спойлер: это не сработает!
Файл: src/main.rs
Листинг 4-6: попытка модификации заимствованной переменной
Вот ошибка:
Как переменные неизменяемы по умолчанию, так и ссылки. Нам не разрешено изменять то, на что у нас есть ссылка.
Изменяемые ссылочные переменные
Мы можем исправить код из листинга 4-6, чтобы позволить себе изменять заимствованное значение, с помощью нескольких небольших настроек, которые используют изменяемую ссылку:
Файл: src/main.rs fn main
() { let s =
String
::from(
"hello"
); change(&s);
} fn change
(some_string: &
String
) { some_string.push_str(
", world"
);
}
$
cargo run
Compiling ownership v0.1.0 (file:///projects/ownership) error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference
-->
src/main.rs:8:5
|
7 | fn change(some_string: &String) {
| ------- help: consider changing this to be a mutable reference: `&mut String`
8 | some_string.push_str(", world");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
For more information about this error, try `rustc --explain E0596`. error: could not compile `ownership` due to previous error
Сначала мы меняем s
на mut
. Затем мы создаём изменяемую ссылку с помощью
&mut s
, у которой вызываем change и обновляем сигнатуру функции, чтобы принять изменяемую ссылку с помощью some_string: &mut String
. Это даёт понять, что change изменит значение, которое заимствует.
Изменяемые ссылки имеют одно большое ограничение: если у вас есть изменяемая ссылка на значение, у вас не может быть других ссылок на это значение. Код, который пытается создать две изменяемые ссылки на s
, завершится ошибкой:
Файл: src/main.rs
Описание ошибки:
Эта ошибка говорит о том, что код недействителен, потому что мы не можем заимствовать s
как изменяемые более одного раза в один момент. Первое изменяемое заимствование находится в r1
и должно длиться до тех пор, пока оно не будет использовано в println!
, но между созданием этой изменяемой ссылки и её
fn main
() { let mut s =
String
::from(
"hello"
); change(&
mut s);
} fn change
(some_string: &
mut
String
) { some_string.push_str(
", world"
);
} let mut s =
String
::from(
"hello"
); let r1 = &
mut s; let r2 = &
mut s; println!
(
"{}, {}"
, r1, r2);
$
cargo run
Compiling ownership v0.1.0 (file:///projects/ownership) 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
For more information about this error, try `rustc --explain E0499`. error: could not compile `ownership` due to previous error
использованием мы попытались создать другую изменяемую ссылку в r2
, которая заимствует те же данные, что и r1
Ограничение, предотвращающее одновременное использование нескольких изменяемых ссылок на одни и те же данные, допускает изменение, но очень контролируемым образом. Это то, с чем борются новые Rustaceans, потому что большинство языков позволяют изменять значение в любой момент. Преимущество этого ограничения заключается в том, что Rust может предотвратить гонку данных во время компиляции. Гонка данных похожа на состояние гонки и происходит, когда возникают следующие три сценария:
Два или больше указателей используют одни и те же данные в одно и то же время,
Минимум один указатель используется для записи данных,
Отсутствуют механизмы для синхронизации доступа к данным.
Гонки данных вызывают неопределённое поведение, и их может быть сложно диагностировать и исправить, когда вы пытаетесь отследить их во время выполнения.
Rust предотвращает такую проблему, отказываясь компилировать код с гонками данных!
Как всегда, мы можем использовать фигурные скобки для создания новой области видимости, позволяющей использовать несколько изменяемых ссылок, но не
одновременно:
Rust применяет аналогичное правило для комбинирования изменяемых и неизменяемых ссылок. Этот код приводит к ошибке:
Ошибка:
let mut s =
String
::from(
"hello"
);
{ let r1 = &
mut s;
}
// r1 goes out of scope here, so we can make a new reference with no problems.
let r2 = &
mut s; let mut s =
String
::from(
"hello"
); let r1 = &s;
// no problem let r2 = &s;
// no problem let r3 = &
mut s;
// BIG PROBLEM
println!
(
"{}, {}, and {}"
, r1, r2, r3);
Вау! У нас также не может быть изменяемой ссылки, пока у нас есть неизменяемая ссылка на то же значение.
Пользователи неизменяемой ссылки не ожидают, что значение внезапно изменится из- под них! Однако разрешены множественные неизменяемые ссылки, потому что никто,
кто просто читает данные, не может повлиять на чтение данных кем-либо ещё.
Обратите внимание, что область действия ссылки начинается с того места, где она была введена, и продолжается до последнего использования этой ссылки. Например, этот код будет компилироваться, потому что последнее использование неизменяемых ссылок println!
, происходит до того, как вводится изменяемая ссылка:
Области неизменяемых ссылок r1
и r2
заканчиваются после println!
, где они использовались в последний раз — то есть до создания изменяемой ссылки r3
. Эти области не пересекаются, поэтому этот код разрешён. Способность компилятора сообщить, что ссылка больше не используется в точке до конца области видимости,
называется нелексическим временем жизни (сокращённо NLL), и вы можете прочитать об этом больше в
The Edition Guide
Несмотря на то, что ошибки заимствования могут иногда вызывать разочарование,
помните, что компилятор Rust заранее указывает на потенциальную ошибку (во время компиляции, а не во время выполнения) и точно показывает, в чем проблема. Тогда вам не придётся выяснять, почему ваши данные оказались не такими, как вы ожидали.
$
cargo run
Compiling ownership v0.1.0 (file:///projects/ownership) 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; // BIG PROBLEM
| ^^^^^^ mutable borrow occurs here
7 |
8 | println!("{}, {}, and {}", r1, r2, r3);
| -- immutable borrow later used here
For more information about this error, try `rustc --explain E0502`. error: could not compile `ownership` due to previous error let mut s =
String
::from(
"hello"
); let r1 = &s;
// no problem let r2 = &s;
// no problem println!
(
"{} and {}"
, r1, r2);
// variables r1 and r2 will not be used after this point let r3 = &
mut s;
// no problem println!
(
"{}"
, r3);
1 ... 4 5 6 7 8 9 10 11 ... 62
For more information about this error, try `rustc --explain E0106`. error: could not compile `ownership` due to previous error this function's return type contains a borrowed value, but there is no value for it to be borrowed from
Давайте подробнее рассмотрим, что именно происходит на каждом этапе нашего кода dangle
:
Файл: src/main.rs
Поскольку s
создаётся внутри dangle
, когда код dangle будет завершён, s
будет освобождена. Но мы попытались вернуть ссылку на неё. Это означает, что эта ссылка будет указывать на недопустимую
String
. Это нехорошо! Rust не позволит нам сделать это.
Решением будет вернуть непосредственно
String
:
Это работает без проблем. Владение перемещено, и ничего не освобождено.
Правила работы с ссылками
Давайте повторим все, что мы обсудили про ссылки:
В один момент времени, может существовать либо одна изменяемая ссылочная переменная, либо любое количество неизменяемых ссылочных переменных,
Все ссылки должны быть действительными.
В следующей главе мы рассмотрим другой тип ссылочных переменных — срезы.
fn dangle
() -> &
String
{
// dangle returns a reference to a String let s =
String
::from(
"hello"
);
// s is a new String
&s
// we return a reference to the String, s
}
// Here, s goes out of scope, and is dropped. Its memory goes away.
// Danger!
fn no_dangle
() ->
String
{ let s =
String
::from(
"hello"
); s
}