Обычно я понимаю, почему заимствованные данные не могут избежать замыкания, но в этом случае я переназначаю вывод части самих себя, поэтому я не пытаюсь присвоить его чему-то внутри замыкания, что могло бы вызвать побег.
Что происходит?
struct A {
a: u8
}
impl A {
pub fn fill_and_write(
&self,
values: &[u8],
mut output: &mut [u8],
) -> Result<usize, ()>{
let len = output.len();
values.iter().try_for_each(|v| {
let written = 3; // write something and return how many was written
output = &mut output[written..];
Ok::<(),()>(())
});
Ok(len - output.len())
}
}
Ошибка:
--> src/lib.rs:14:13
|
9 | mut output: &mut [u8],
| ---------- `output` declared here, outside of the closure body
...
14 | output = &mut output[written..];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error[E0597]: `output` does not live long enough
Причина тонкая.
С точки зрения компилятора &mut output
и output =
— две разные операции. Первый выходит из output
, второй вставляет значение обратно. И между ними могут произойти произвольные вещи: например, что, если written
находится за пределами поля? Обратный вызов запаникует, и output
останется неинициализированным. Может кто-то подхватит панику и воспользуется неинициализированным output
. Это плохо.
try_for_each()
принимает замыкание FnMut
, что означает, что он не может оставить переменные неинициализированными (даже временно, из-за паники, как мы видели), то есть он не может их использовать или они не могут избежать его. Это то же самое, как если бы замыкание содержало только изменяемую ссылку на output
, а не собственное значение (не случайно: FnOnce
принимает self
по значению, и поэтому все захваченные переменные обрабатываются так, как будто они принадлежат. FnMut
принимает &mut self
, и так все захваченные переменные обрабатываются так, как будто они находятся за изменяемой ссылкой, а Fn
принимает &self
, поэтому все захваченные переменные обрабатываются так, как будто они находятся за общей ссылкой).
Но есть другой путь к успеху: перезаимствование. У нас есть output: &mut &mut [u8]
, и мы перезаимствуем его, чтобы получить &mut [u8]
.
Проблема повторного заимствования заключается в том, что повторное заимствование &'a mut &'b mut T
может дать вам лишь кратковременную ссылку &'a mut T
. Итак, если у нас есть output: &'b mut [u8]
и есть ссылка на него &'a mut &'b mut [u8]
, мы можем получить только &'a mut [u8]
. Но мы пытаемся присвоить его output
, то есть &'b mut [u8]
; жизнь не живет достаточно долго. Это аналогичная проблема, с которой можно столкнуться при попытке реализовать изменяемые итераторы.
И исправление тоже такое же (без unsafe): временно удалить значение из ссылки, чтобы оно принадлежало вам и имело полный срок службы. То есть:
pub fn fill_and_write(&self, values: &[u8], mut output: &mut [u8]) -> Result<usize, ()> {
let len = output.len();
values.iter().try_for_each(|v| {
let written = 3; // write something and return how many was written
let my_output = std::mem::take(&mut output);
output = &mut my_output[written..];
Ok::<(), ()>(())
});
Ok(len - output.len())
}
@cafce25 Я представил два варианта, два способа, которыми программист может неправильно понять и подумать, что это сработает: либо если он думает, что оно движется (и я объясняю, почему это не работает), либо если они думают, что оно перезаимствует (и я объяснил, почему это тоже не могу работать).
Я думаю, что этот ответ не очень ясен. Если «движущаяся» часть — это именно то, что программист может ошибочно подумать, то, вероятно, ее следует представить как таковую.
Действительно ли заимствование выходит из заимствованного из переменной? Мне это кажется очень неправильным.