Заимствованные данные выходят за пределы замыкания, переназначая буфер на фрагмент самого себя при замыкании

Обычно я понимаю, почему заимствованные данные не могут избежать замыкания, но в этом случае я переназначаю вывод части самих себя, поэтому я не пытаюсь присвоить его чему-то внутри замыкания, что могло бы вызвать побег.

Что происходит?

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
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
1
0
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Причина тонкая.

С точки зрения компилятора &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 01.09.2024 17:11

@cafce25 Я представил два варианта, два способа, которыми программист может неправильно понять и подумать, что это сработает: либо если он думает, что оно движется (и я объясняю, почему это не работает), либо если они думают, что оно перезаимствует (и я объяснил, почему это тоже не могу работать).

Chayim Friedman 01.09.2024 17:14

Я думаю, что этот ответ не очень ясен. Если «движущаяся» часть — это именно то, что программист может ошибочно подумать, то, вероятно, ее следует представить как таковую.

jthulhu 01.09.2024 18:06

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