Обмен двух локальных ссылок приводит к пожизненной ошибке
У меня есть две переменные типа &T
, x
а также y
, который я поменяю локально внутри функции:
pub fn foo<T: Copy>(mut x: &T) {
let y_owned = *x;
let mut y = &y_owned;
for _ in 0..10 {
do_work(x, y);
std::mem::swap(&mut x, &mut y);
}
}
fn do_work<T>(_x: &T, _y: &T) {}
Этот код не компилируется, выдавая следующую ошибку:
error[E0597]: `y_owned` does not live long enough
--> src/lib.rs:3:22
|
3 | let mut y = &y_owned;
| ^^^^^^^ borrowed value does not live long enough
...
8 | }
| - borrowed value only lives until here
|
note: borrowed value must be valid for the anonymous lifetime #1 defined on the function body at 1:5...
--> src/lib.rs:1:5
|
1 | / pub fn foo<T: Copy>(mut x: &T) {
2 | | let y_owned = *x;
3 | | let mut y = &y_owned;
4 | | for _ in 0..10 {
... |
7 | | }
8 | | }
| |_____^
Я не понимаю, почему это не должно работать. x
а также y
имеют разные времена жизни, но зачем компилятору требовать y
жить до тех пор, пока x
? Я только изменяю ссылки локально внутри foo
и ссылочные объекты гарантированно существуют. однажды foo
возвращается, не имеет значения, если эти x
а также y
даже существовал, не так ли?
Для более широкого контекста я реализую сортировку слиянием и хочу поменять местами основной и вспомогательный (временные) массивы таким образом.
4 ответа
Очевидно, что
x
а такжеy
имеют разные времена жизни, но зачем компилятору требоватьy
жить до тех пор, покаx
?
Из-за подписи std::mem::swap
:
pub fn swap<T>(x: &mut T, y: &mut T)
T
тип аргумента для foo
, который является ссылкой на некоторое время жизни, выбранное вызывающим foo
, В выпуске Rust 2018 года последний компилятор выдает немного более подробное сообщение об ошибке, в котором он вызывает это время жизни. '1
, призвание std::mem::swap
требует тип x
, &'1 T
быть таким же, как тип y
, но это не может сократить продолжительность жизни x
чтобы соответствовать y
потому что жизнь x
выбирается абонентом, а не foo
сам. Ответ Викрама более подробно объясняет, почему продолжительность жизни не может быть сокращена в этом случае.
По сути, я только изменяю ссылки локально внутри foo, и ссылочные объекты гарантированно существуют
Это правда, но это не дает вам никакой свободы в отношении жизни x
внутри foo
, Делать foo
компиляция, вы должны дать компилятору еще одну степень свободы, сделав новый заем, из которого компилятор может выбрать время жизни. Эта версия будет компилировать ( детская площадка):
pub fn foo<T: Copy>(x: &T) {
let mut x = &*x;
...
}
Это называется повторным заимствованием, и в некоторых случаях это происходит неявно, например, с получателем вызова метода, который принимает &mut self
, Это не происходит неявно в том случае, если вы представили, потому что swap
это не метод.
Полезно скомпилировать эту программу с последним стабильным набором инструментов в выпуске 2018 года, поскольку она немного улучшает сообщение об ошибке:
error[E0597]: `y_owned` does not live long enough
--> src/lib.rs:4:17
|
1 | pub fn foo<T: Copy>(mut x: &T) {
| - let's call the lifetime of this reference `'1`
...
4 | let mut y = &y_owned;
| ^^^^^^^^
| |
| borrowed value does not live long enough
| assignment requires that `y_owned` is borrowed for `'1`
...
9 | }
| - `y_owned` dropped here while still borrowed
Что происходит, это:
- вход
x
ссылка с произвольным временем жизни'1
установлено вызывающим абонентом. - Переменная
y
является ссылкой, созданной локально, и как таковая она имеет срок жизни "короче", чем'1
,
Таким образом, вы не можете передать ссылку в y
в x
даже если это может показаться безопасным, потому что x
ожидает чего-то, что живет, по крайней мере, в течение жизни, указанной вызывающим абонентом.
Одним из возможных решений является создание второй копии значения за x и заимствование ее локально.
pub fn foo<T: Copy>(x: &T) {
let mut x = &*x;
let mut y = &*x;
for _ in 0..10 {
do_work(x, y);
std::mem::swap(&mut x, &mut y);
}
}
Изменчивые ссылки инвариантны по отношению к типу, на который они ссылаются. Если у вас есть &'a mut T
то оно инвариантно T
, Подпись swap()
ожидает одинаковые типы с одинаковыми временами жизни на обоих входных аргументах. то есть они оба являются изменяемыми ссылками на T
,
Давайте посмотрим на вашу проблему:
Аргумент к foo()
является &T
и со временем это будет foo<'a, T: Copy>(mut x: &'a T)
и эта жизнь дается вызывающей стороной. Внутри функции у вас есть локальная переменная y_owned
и вы берете ссылку на это с какой-то локальной жизнью. Итак, на данный момент мы имеем &'a T
который является входным аргументом с временем жизни, установленным вызывающим и &'local y_owned
с какой-то локальной жизнью. Все хорошо!
Далее вы звоните swap()
и перейдите к нему, изменяемые ссылки (&mut &T
а также &mut &y_owned
) к вышеупомянутым ссылкам. Теперь вот подвох; Поскольку они являются изменчивыми ссылками и, как уже упоминалось, они инвариантны относительно того, на что они указывают; x
который &'a T
не будет сокращаться до объема вызова функции, в результате y
который &'local y_owned
теперь также ожидается, что &'a y_owned
, что невозможно, так как 'a
выходит за рамки y_owned
следовательно, он жалуется, что y_owned
не живет достаточно долго.
Для получения дополнительной информации, пожалуйста, обратитесь к этому
Информация о жизни ссылки является частью ее типа. Поскольку Rust является языком статической типизации, время жизни ссылочной переменной не может динамически изменяться во время выполнения.
Время жизни ссылки x
определяется вызывающей стороной, и она должна быть длиннее, чем все, что создано внутри функции. Время жизни y
время жизни переменной, локальной для функции, и, следовательно, короче, чем время жизни x
, Поскольку эти два времени жизни не совпадают, вы не можете поменять местами переменные, поскольку вы не можете динамически изменять тип переменной, а время жизни является частью ее типа.