Идиоматический способ написать декоратор поверх итератора в Rust

Я новичок в Rust и все еще борюсь с проверкой заимствований.

Я использую этот вопрос как общую возможность обучения. Итак, я был бы признателен за объяснение, применимое в целом к ​​аналогичной ситуации, а не за ответ, который просто применим к этому конкретному примеру.

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

Вот как выглядит мой код-оболочка:

use std::{fs::File, path::Path};

use parquet::{
    file::{reader::FileReader, serialized_reader::SerializedFileReader},
    record::reader::RowIter,
};

pub struct PIterator<'a> {
    row_iterator: RowIter<'a>,
}

impl<'a> PIterator<'a> {
    pub fn new(file_path: String) -> PIterator<'a> {
        let path = Path::new(&file_path);
        let file = File::open(path).unwrap();
        let reader = SerializedFileReader::new(file).unwrap();
        let row_iter = reader.get_row_iter(None);  // reader is borrowed here
        PIterator {  // cannot return value referencing local variable `reader`...
            row_iterator: row_iter.unwrap(),
        }
    }

    pub fn has_next(&self) -> bool {
        // TODO:
        false
    }

    pub fn next(&self) -> Option<String> {
        // TODO:
        None
    }
}

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

Чего я не могу понять, так это того, кто здесь «заемщик»? Это метод «новый»? Как мы это определяем?

Кроме того, каков идиоматический способ переноса итератора в этой ситуации?

«кто здесь заемщик» — OldIterator имеет параметр времени жизни, что обязательно означает, что он у чего-то занимает. Скорее всего заимствование происходит во время .get_row_iter() и является заимствованием row_iter (но виды не знаю, чтобы знать наверняка, вы ими не поделились).

kmdreko 20.06.2024 21:36

Ваше определение new бесполезно. Даже не глядя на реализацию, тип fn new(file_path: String) -> NewIterator<'a> не имеет нетривиальных реализаций, то есть он не может фактически создать ссылку для любого возможного времени жизни, поэтому эта ссылка обязательно является фантомной. Возможно, вам повезет больше, например. fn new(cfr : &'a CustomFileReader) -> NewIterator<'a>

user2407038 20.06.2024 21:38

Я предполагаю, что это get_row_iter ? Вы можете использовать RowIter::from_file_into , чтобы получить собственный RowIter<'static>. play.rust-lang.org/… Если это не тот тип, поищите что-то похожее.

drewtato 20.06.2024 21:48

В любом случае отредактируйте свой вопрос, включив в него фактические типы и функции (ссылки на документы, если они из общедоступного ящика, или определения, если они ваши собственные), которые вы используете в new.

drewtato 20.06.2024 21:48
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
0
4
74
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

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

Ваша проблема в том, что в PIterator вы пытаетесь сослаться на что-то, что будет удалено. Вы создаете RowIter, который ссылается на reader, а затем пытаетесь вернуть его, хотя reader будет удалено в конце new.

Вместо того, чтобы ссылаться на него, передайте право собственности на него. Чтобы создать итератор, который владеет своими данными, а не просто ссылается на них, вы идиоматически используете функцию into_iter из признака IntoIter. Как только вы это сделаете, вы можете удалить время жизни из своей структуры.

Затем поместите свою функцию next в блок Iterator impl, чтобы язык знал, что это должна быть Iterator::next, а не функция, имя которой случайно получилось next. Что касается has_next, вам не обязательно реализовывать это самостоятельно, просто используйте существующий адаптер peekable.

Некоторые другие незначительные изменения, которые я сделал, - это (более) правильная обработка ошибок вместо unwrap и &Path вместо ненужного String в качестве аргумента для new (если у вас уже есть Path, вам придется без необходимости преобразовать его обратно в String только для вызова new...).

use std::{fs::File, io, path::Path};

use parquet::{file::serialized_reader::SerializedFileReader, record::reader::RowIter};
use thiserror::Error;

pub struct PIterator {
    row_iterator: RowIter<'static>,
}

#[derive(Debug, Error)]
pub enum PIteratorCreationError {
    #[error(transparent)]
    IOError(#[from] io::Error),

    #[error(transparent)]
    ParquetError(#[from] parquet::errors::ParquetError),
}

impl PIterator {
    pub fn new(file_path: &Path) -> Result<Self, PIteratorCreationError> {
        let file = File::open(file_path)?;
        let reader = SerializedFileReader::new(file)?;
        let row_iter = reader.into_iter();

        Ok(PIterator { row_iterator: row_iter })
    }
}

impl Iterator for PIterator {
    type Item = <RowIter<'static> as Iterator>::Item;

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

fn main() {
    let p_iterator = match PIterator::new(Path::new("Some filename...")) {
        Ok(iterator) => iterator,
        Err(creation_error) => match creation_error {
            PIteratorCreationError::IOError(io_error) => {
                // handle io error here somehow
                eprintln!("IO error: {io_error}");
                return;
            }
            PIteratorCreationError::ParquetError(parquet_error) => {
                // handle parquet error here somehow
                eprintln!("Parquet error: {parquet_error}");
                return;
            }
        },
    };

    let mut iterator = p_iterator.peekable();

    let has_next: bool = iterator.peek().is_some();
}

Спасибо за ответ. Прежде чем прочитать это, я заставил PIterator принимать ввод файла из main. Но это гораздо элегантнее. Думаю, я был незнаком с «into_iter», который решает проблему владения в этом вопросе. Отличный ответ.

Lone Wolf 24.06.2024 14:29

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