Обмен двух локальных ссылок приводит к пожизненной ошибке

У меня есть две переменные типа &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, Поскольку эти два времени жизни не совпадают, вы не можете поменять местами переменные, поскольку вы не можете динамически изменять тип переменной, а время жизни является частью ее типа.

Другие вопросы по тегам