Я новичок в 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
}
}
Я понимаю, что когда метод вызывается для изменяемой ссылки, объект может мутировать, оставляя ссылки висящими. Итак, передача права собственности должна произойти.
Чего я не могу понять, так это того, кто здесь «заемщик»? Это метод «новый»? Как мы это определяем?
Кроме того, каков идиоматический способ переноса итератора в этой ситуации?
Ваше определение new
бесполезно. Даже не глядя на реализацию, тип fn new(file_path: String) -> NewIterator<'a>
не имеет нетривиальных реализаций, то есть он не может фактически создать ссылку для любого возможного времени жизни, поэтому эта ссылка обязательно является фантомной. Возможно, вам повезет больше, например. fn new(cfr : &'a CustomFileReader) -> NewIterator<'a>
Я предполагаю, что это get_row_iter ? Вы можете использовать RowIter::from_file_into , чтобы получить собственный RowIter<'static>
. play.rust-lang.org/… Если это не тот тип, поищите что-то похожее.
В любом случае отредактируйте свой вопрос, включив в него фактические типы и функции (ссылки на документы, если они из общедоступного ящика, или определения, если они ваши собственные), которые вы используете в new
.
В этом конкретном примере я бы посоветовал вам просто создать функцию, которая создает для вас 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», который решает проблему владения в этом вопросе. Отличный ответ.
«кто здесь заемщик» —
OldIterator
имеет параметр времени жизни, что обязательно означает, что он у чего-то занимает. Скорее всего заимствование происходит во время.get_row_iter()
и является заимствованиемrow_iter
(но виды не знаю, чтобы знать наверняка, вы ими не поделились).