Подтвердить что ты не робот

Инкапсулирование последовательно инициализированного состояния с саморекламами в структуре Rust

Я пытаюсь определить структуру, которая может выступать в качестве итератора для Vec, который хранится внутри RefCell:

use std::slice::Iter;
use std::cell::Ref;
use std::cell::RefCell;

struct HoldsVecInRefCell {
    vec_in_refcell: RefCell<Vec<i32>>,
}

// TODO: struct HoldsVecInRefCellIter implementing Iterator ...

impl HoldsVecInRefCell {
    fn new() -> HoldsVecInRefCell {
        HoldsVecInRefCell { vec_in_refcell: RefCell::new(Vec::new()) }
    }

    fn add_int(&self, i: i32) {
        self.vec_in_refcell.borrow_mut().push(i);
    }

    fn iter(&self) -> HoldsVecInRefCellIter {
        // TODO ...
    }
}

fn main() {
    let holds_vec = HoldsVecInRefCell::new();
    holds_vec.add_int(1);
    holds_vec.add_int(2);
    holds_vec.add_int(3);

    let mut vec_iter = holds_vec.iter();  // Under the hood: run-time borrow check

    for i in vec_iter {
        println!("{}", i);
    }
}

Для сравнения, vec_iter может быть инициализирован в строке в main() следующим образом (намеренно многословно):

// Elided: lifetime parameter of Ref
let vec_ref: Ref<Vec<i32>> = holds_vec.vec_in_refcell.borrow();
// Elided: lifetime parameter of Iter
let mut vec_iter: Iter<i32> = vec_ref.iter();

Есть ли способ определить реализацию структуры Iterator, которая содержит как Ref (чтобы сохранить неизменяемый RefCell заимствованный живой), так и Iter (для сохранения состояния итератора для next(), а не катив мой собственный итератор для Vec или любого другого контейнера), когда вторая выведена из (и содержит ссылку, полученную из) первой?

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

struct HoldsVecInRefCellIter<'a> {
    vec_ref: Ref<'a, Vec<i32>>,
    vec_iter: Iter<'a, i32>,
}

то я не могу инициализировать оба поля сразу с синтаксисом HoldsVecInRefCellIter { ... } (см., например, Есть ли у Rust синтаксис для инициализации поля структуры с более ранним полем?). Если я попытаюсь отключить последовательную инициализацию с помощью структуры типа

struct HoldsVecInRefCellIter<'a> {
    vec_ref: Ref<'a, Vec<i32>>,
    vec_iter: Option<Iter<'a, i32>>,
}

// ...

impl HoldsVecInRefCell {
    // ...

    fn iter(&self) -> HoldsVecInRefCellIter {
        let mut new_iter = HoldsVecInRefCellIter { vec_ref: self.vec_in_refcell.borrow(), vec_iter: None };
        new_iter.vec_iter = new_iter.vec_ref.iter();
        new_iter
    }
}

то я беру на себя измененный самозахват структуры, который не позволяет вернуть его из iter(). Это самозахват структуры также может произойти, если вы попытаетесь сохранить ссылку на одну часть структуры в самой структуре (Почему я не могу сохранить значение и ссылку на это значение в той же структуре?), что предотвратит безопасное перемещение экземпляров структуры. Для сравнения, это похоже на структуру типа HoldsVecInRefCellIter, если вы можете завершить инициализацию, выполнит правильную вещь при перемещении, так как все ссылки внутренне относятся к данным в другом месте, которые переживают эту структуру.

Есть трюки, чтобы избежать создания самореференций с помощью Rc (см. примеры в https://internals.rust-lang.org/t/self-referencing-structs/418/3), но я не как они могут применяться, если вы хотите сохранить существующую структуру Iterator, которая реализована для прямой ссылки на базовый контейнер, а не на Rc.

Как новичок Rust из С++, это похоже на проблему, которая часто возникала ( "У меня есть логика инициализации сложного состояния в блоке кода, и я хочу отвлечь эту логику и удерживать полученное состояние в структура для использования" ).

Связанный вопрос: Возвращение итератора Vec в RefCell

4b9b3361

Ответ 1

Нам придется обманывать и лгать о жизни.

use std::mem;

struct HoldsVecInRefCellIter<'a> {
    vec_ref: Ref<'a, Vec<i32>>,
    vec_iter: Iter<'a, i32>, // 'a is a lie!
}

impl HoldsVecInRefCell {
    fn iter(&self) -> HoldsVecInRefCellIter {
        unsafe {
            let vec_ref = self.vec_in_refcell.borrow();
            // transmute changes the lifetime parameter on the Iter
            let vec_iter = mem::transmute(vec_ref.iter());
            HoldsVecInRefCellIter { vec_ref: vec_ref, vec_iter: vec_iter }
        }
    }
}

impl<'a> Iterator for HoldsVecInRefCellIter<'a> {
    type Item = i32;

    fn next(&mut self) -> Option<Self::Item> {
        self.vec_iter.next().cloned()
    }
}

Это работает только потому, что Iter не отменяется, перемещая Ref, так как Ref указывает на Vec, а Iter указывает на хранилище Vec, а не на Ref.

Однако это также позволяет перемещать vec_iter из HoldsVecInRefCellIter; если вы извлекаете vec_iter и отбрасываете vec_ref, тогда заем будет освобожден, а Iter может быть недействительным без Rust, дающего ошибку компилятора ('a - это время жизни RefCell). При правильной инкапсуляции вы можете сохранить содержимое структуры конфиденциальной и не позволять пользователям выполнять эту небезопасную операцию.

Кстати, мы могли бы точно определить итератор для возврата ссылок:

impl<'a> Iterator for HoldsVecInRefCellIter<'a> {
    type Item = &'a i32;

    fn next(&mut self) -> Option<Self::Item> {
        self.vec_iter.next()
    }
}