ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 11.01.2024
Просмотров: 1131
Скачиваний: 5
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
Листинг 4-3: функции с аннотированными владением и областью видимости
Если бы мы попытались использовать s
после вызова takes_ownership
, Rust выдал бы ошибку во время компиляции. Эти статические проверки защищают нас от ошибок.
Попробуйте добавить в main код, который использует s
и x
, чтобы увидеть, где вы можете их использовать, а где правила владения не позволяют вам это сделать.
Возвращение значений и область видимости
Возвращаемые значения также могут передавать право владения. В листинге 4-4
показан пример функции, возвращающей некоторое значение, с такими же аннотациями, как в листинге 4-3.
Файл: src/main.rs fn main
() { let s =
String
::from(
"hello"
);
// s comes into scope takes_ownership(s);
// s's value moves into the function...
// ... and so is no longer valid here let x =
5
;
// x comes into scope makes_copy(x);
// x would move into the function,
// but i32 is Copy, so it's okay to still
// use x afterward
}
// Here, x goes out of scope, then s. But because s's value was moved, nothing
// special happens.
fn takes_ownership
(some_string:
String
) {
// some_string comes into scope println!
(
"{}"
, some_string);
}
// Here, some_string goes out of scope and `drop` is called. The backing
// memory is freed.
fn makes_copy
(some_integer: i32
) {
// some_integer comes into scope println!
(
"{}"
, some_integer);
}
// Here, some_integer goes out of scope. Nothing special happens.
Листинг 4-4: передача права владения на возвращаемые значения
Владение переменной каждый раз следует одному и тому же шаблону: присваивание значения другой переменной перемещает его. Когда переменная, содержащая данные в куче, выходит из области видимости, содержимое в куче будет очищено функцией drop
,
если только данные не были перемещены во владение другой переменной.
Хотя это работает, получение права владения, а затем возвращение владения каждой функцией немного утомительно. Что, если мы хотим, чтобы функция использовала значение, но не становилась владельцем? Очень раздражает, что всё, что мы передаём,
также должно быть передано обратно, если мы хотим использовать это снова, в дополнение к любым данным, полученным из тела функции, которые мы также можем захотеть вернуть.
Rust позволяет нам возвращать несколько значений с помощью кортежа, как показано в листинге 4-5.
Файл: src/main.rs fn main
() { let s1 = gives_ownership();
// gives_ownership moves its return
// value into s1
let s2 =
String
::from(
"hello"
);
// s2 comes into scope let s3 = takes_and_gives_back(s2);
// s2 is moved into
// takes_and_gives_back, which also
// moves its return value into s3
}
// Here, s3 goes out of scope and is dropped. s2 was moved, so nothing
// happens. s1 goes out of scope and is dropped.
fn gives_ownership
() ->
String
{
// gives_ownership will move its
// return value into the function
// that calls it let some_string =
String
::from(
"yours"
);
// some_string comes into scope some_string
// some_string is returned and
// moves out to the calling
// function
}
// This function takes a String and returns one fn takes_and_gives_back
(a_string:
String
) ->
String
{
// a_string comes into
// scope a_string
// a_string is returned and moves out to the calling function
}
Листинг 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.
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);
, которая заимствует те же данные, что и 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