Как убедить заемщика в том, что заимствованная ссылка больше не используется - PullRequest
0 голосов
/ 27 января 2020

Я ищу способ убедить контролера заимствования, что следующий код безопасен.

Ситуация

SliceHolder - это структура, и я не могу изменить ее интерфейс (отличается библиотека). Я хочу временно установить срез в SliceHolder, вызвать некоторый метод, который будет обрабатывать данные, а затем завершать заимствование sh (прекратить заимствование среза). Моя проблема в том, что этот заемщик не позволяет мне это делать. Если я добавлю в интерфейс foo where 'a: 'b, проверка заимствования не будет выполнена на self.slice = slice;, потому что входной срез не может быть сохранен (даже временный) в структуре, что увеличит срок его службы (по крайней мере, я думаю, что это причина). Если я перейду на where 'b: 'a, средство проверки заимствований выйдет из строя на x[0] = 5, так как считает, что срез заимствован дольше, чем время жизни SliceHolder.

Я просто хочу временно использовать ссылку на срез и каким-то образом освободить ссылка, когда функция foo заканчивается, поэтому средство проверки заимствования не будет считать ссылку использованной. Для этого я использовал self.slice = &DEFAULT;, который должен убедить заемщика проверять, что slice больше не хранится внутри SliceHolder, но это не работает.

Я нашел решение с небезопасными и необработанными указателями (self.slice = unsafe {&*(slice as *const [u8])}; ) но я думаю, что это не правильный способ, как решить эту проблему в ржавчине.

код

struct SliceHolder<'a> {
    slice: &'a [u8],
}

const DEFAULT: [u8; 0] = [];

impl<'a> SliceHolder<'a> {
    fn foo<'b>(&mut self, slice: &'b [u8]) -> Vec<u8> {
        self.slice = slice; // FIRST POSITION WHERE BORROW CHECKER COMPLAINS
        let result = do_something_with_holder(&self);
        self.slice = &DEFAULT;
        result
    }
}

// blackbox function, do some computation based on the slice
fn do_something_with_holder(holder: &SliceHolder) -> Vec<u8> {
    Vec::from(holder.slice)
}


fn main() {
    let mut holder = SliceHolder {
        slice: &DEFAULT,
    };

    let mut x: [u8; 1] = [1];
    holder.foo(&x);
    x[0] = 5;
    holder.foo(&x); // SECOND POSITION WHERE BORROW CHECKER COMPLAINS
}

пробовал с ржавчиной 1.40.

1 Ответ

3 голосов
/ 27 января 2020

При попытке решить такие проблемы, связанные со временем жизни, полезно составить диаграмму всех вовлеченных времен жизни. Есть 2 названных времени жизни, но минимум 5 соответствующих времен жизни. Это только для функции SliceHolder::foo.

  • 'static. Это время жизни, которое будет иметь &DEFAULT (в связи с продвижением 'static ).
  • 'a. Время жизни привязано к типу self.
  • 'b. Время жизни среза.
  • Анонимное время жизни 1 (я назову это 'c). Время жизни &mut заимствовано на self.
  • Анонимное время жизни 2 (назову это 'd). Время жизни, соответствующее телу функции.

Вот схема их взаимоотношений (простите за дерьмовый ASCII-арт). Время жизни выше на диаграмме обычно длится дольше, но время жизни длится дольше, только если есть последовательность стрелок, соединяющих их.

        'static
           ^
         /   \
       /       \
     /          \
    |            |
    V            V
   'a           'b
    |            |
    V            |
   'c            |
     \          /
       \      /
         \  /
          V
         'd

Добавление ограничения времени жизни 'b: 'a равносильно добавлению стрелки 'a -> 'b на эта диаграмма, говорящая о том, что 'a переживает 'b или заимствует с временем жизни 'a, действительна для всего периода 'b.

Давайте посмотрим, как эти времена жизни взаимодействуют в main .

// holder: SliceHolder<'a>, where 'a: 'static
let mut holder = SliceHolder { slice: &DEFAULT };

let mut x: [u8; 1] = [1];

// Take a mutable borrow of holder (with lifetime 'c1)
// Also take a borrow of x with lifetime 'b1
holder.foo(&x);
// mutate x. This forces 'b1 to end no later than here.
x[0] = 5;
// Take another mutable borrow of holder ('c2)
// and another borrow of x ('b2)
holder.foo(&x);

Это показывает, что у нас не может быть 'b: 'a, поскольку это будет означать, что 'a должен закончиться не позднее, чем когда 'b закончится. Это заставит holder прекратить существование до того, как x будет мутирован. Но holder используется позже этого.


Итак, мы показали, что у нас не может быть 'b: 'a. Что тогда осталось? Когда мы выполняем присваивание self.slice = slice;, мы неявно приводим значение от &'b [u8] к &'a [u8]. Для этого требуется 'b: 'a, что мы только что исключили. self всегда должен иметь тип SliceHolder<'a>, поэтому мы не можем просто сократить его время жизни.

В сторону: если мы не поддержали это, то срез в self всегда имеет время жизни (по крайней мере) 'a, мы могли бы в какой-то момент (* например, в do_something_with_holder) преобразовать c и избежать контрольного пути, где self.slice переназначается чему-либо с более длинным временем жизни. 'b закончится (аннулирует slice: &'b [u8]), но self все еще будет существовать и будет содержать недопустимую ссылку. Это то, что вам нужно учитывать, если вы когда-либо используете код unsafe.

Однако у нас может быть вторая переменная с более коротким временем жизни, чье значение (т.е. внутренний срез) совпадает с self. Нам нужно, чтобы эта вторая переменная имела тип SliceHolder<'_> с некоторым временем жизни, не превышающим 'a (чтобы мы могли использовать ссылку self) и не длиннее 'b ( поэтому мы можем присвоить slice его срезу). Мы смотрим на диаграмму и видим, что время жизни действительно меньше, чем у 'a и 'b, а именно 'd.

К счастью, нам не нужно беспокоиться об именовании этого времени жизни. Все, что имеет значение, так это то, что он существует, а остальное компилятор выяснит. Итак, как мы можем получить эту вторую переменную? Нам нужно как-то переместить self в новую переменную. Но помните, мы не можем выйти из изменчивых ссылок, поэтому мы должны оставить внутри что-то действительное.

Мы уже планировали отключить ссылку self для &DEFAULT, так почему бы не просто сделай это? Соответствующая команда - std::mem::replace и может использоваться как let second_variable = std::mem::replace(self, SliceHolder {slice: &DEFAULT}).

. Есть несколько более эргономичных c способов сделать это, но это потребует добавления некоторых черт для SliceHolder. Если SliceHolder действительно содержит только ссылку, он может реализовать Copy, что делает все в этой ситуации проще. За исключением этого, реализация Default для него (с &DEFAULT в качестве среза по умолчанию) позволит вам использовать вновь стабилизированный std::mem::take вместо std::mem::replace. Тогда вам не нужно будет создавать это значение по умолчанию inline.

Возможно, есть некоторые другие вещи, которые необходимо учитывать, но без минимального воспроизводимого примера трудно сказать, что. Я оставлю вас с некоторым рабочим кодом (детская площадка) .

struct SliceHolder<'a> {
    slice: &'a [u8],
}

const DEFAULT: [u8; 0] = [];

struct Result;

fn do_something_with_holder(_: &SliceHolder) -> Result {
    Result
}

impl<'a> SliceHolder<'a> {
    fn foo<'b>(&mut self, slice: &'b [u8]) -> Result {
        // new_holder has the exact value that self would have had before
        let mut new_holder: SliceHolder<'_> =
            std::mem::replace(self, SliceHolder { slice: &DEFAULT });

        new_holder.slice = slice;
        let result = do_something_with_holder(&new_holder);
        // self.slice = &DEFAULT; // no longer needed - we've already taken care of that
        result
    }
}

fn main() {
    let mut holder = SliceHolder { slice: &DEFAULT };

    let mut x: [u8; 1] = [1];
    holder.foo(&x);
    x[0] = 5;
    holder.foo(&x);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...