Обмен двух локальных ссылок приводит к пожизненной ошибке - PullRequest
0 голосов
/ 18 декабря 2018

У меня есть две переменные типа &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, не так ли?

Для более широкого контекста я реализую mergesort и хочу поменять местами основной и вспомогательный (временный) таким образом.

Ответы [ 4 ]

0 голосов
/ 18 декабря 2018

Изменяемые ссылки инвариантны по отношению к типу, к которому они относятся.Если у вас &'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 не живет достаточно долго.

Для получения дополнительной информации, пожалуйста, обратитесь к этому

0 голосов
/ 18 декабря 2018

Очевидно, что 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 не является методом.

0 голосов
/ 18 декабря 2018

Информация о времени жизни ссылки является частью ее типа.Поскольку Rust является языком статической типизации, время жизни ссылочной переменной не может динамически изменяться во время выполнения.

Время жизни ссылки x определяется вызывающей стороной, и оно должно быть длиннее, чем все, чтосоздается внутри функции.Время жизни y - это время жизни переменной, локальной для функции, и поэтому оно короче, чем время жизни x.Поскольку эти два времени жизни не совпадают, вы не можете поменять местами переменные, поскольку вы не можете динамически изменить тип переменной, а время жизни является частью ее типа.

0 голосов
/ 18 декабря 2018

Полезно скомпилировать эту программу с последним стабильным набором инструментов в выпуске 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);
    }
}
...