Детская площадка
Контекст
Я хочу предварительно обработать строки текстового файла перед передачей их в функцию f
.Я могу сделать это так:
pub fn example0a<B: BufRead, F: Fn(&str)>(bufread: B, f: F) {
let name = Regex::new("John Doe").unwrap();
for line in bufread.lines() {
let line = line.unwrap();
let pre_processed_line = name.replace_all(&line, "XXX");
f(&pre_processed_line);
}
}
, но мне нужно создать объект с помощью метода for_each
, которому я мог бы напрямую передать f
.Моей первой идеей было создание итератора с использованием метода map
:
// does not compile
pub fn example0b<B: BufRead>(bufread: B) -> impl Iterator {
let name = Regex::new("John Doe").unwrap();
bufread.lines().map(move |line| {
let line = line.unwrap();
let pre_processed_line = name.replace_all(&line, "XXX");
&pre_processed_line as &str;
})
}
Это не компилируется, потому что line
и, следовательно, pre_processed_line
не живут достаточно долго, чтобы быть возвращенными из итератора next
метод.Один из вариантов - вернуть pre_processed_line.to_string()
, но это не очень хорошо, потому что он клонирует все строки, которые не были изменены с помощью replace_all
, чего я хочу избежать.
Моя первая структура
Я решил реализовать структуру, содержащую BufRead
и функцию предварительной обработки и обеспечивающую метод for_each
.Я стремился сделать его как можно более универсальным, чтобы фактически он принимал любой итератор любого типа, при условии, что функция предварительной обработки может преобразовать его в &str
.
pub struct TransformedStrStream<S, FT>
where
S: Iterator,
FT: FnMut(S::Item, &mut FnMut(&str)),
{
source: S,
transf: FT,
}
impl<S, FT> TransformedStrStream<S, FT>
where
S: Iterator,
FT: FnMut(S::Item, &mut FnMut(&str)),
{
pub fn for_each<F>(self, mut f: F)
where
F: FnMut(&str),
{
let source = self.source;
let mut transf = self.transf;
source.for_each(move |line| transf(line, &mut f));
}
}
.создать экземпляр этой структуры аналогично приведенным выше примерам:
pub fn example1<B: BufRead>(bufread: B, name: Regex) {
let _ = TransformedStrStream {
source: bufread.lines(),
transf: move |line, f| {
let line = line.unwrap();
let repl = name.replace_all(&line, "XXX");
f(&repl as &str)
},
};
}
Моя проблема
Структура, приведенная выше, является, я думаю, хорошей абстракцией и может быть абстрагирована еще дальше для генерациилюбой тип значения (вместо &str
).
Я пытался заменить &str
параметром типа T
:
pub struct TransformedStream<S, FT, T>
where
S: Iterator,
FT: FnMut(S::Item, &mut FnMut(T)),
{
source: S,
transf: FT,
phantom: PhantomData<T>,
}
impl<S, FT, T> TransformedStream<S, FT, T>
where
S: Iterator,
FT: FnMut(S::Item, &mut FnMut(T)),
{
pub fn for_each<F>(self, mut f: F)
where
F: FnMut(T),
{
let source = self.source;
let mut transf = self.transf;
source.for_each(move |line| transf(line, &mut f));
}
}
К сожалению, мой пример выше не компилируетсябольше:
pub fn example2<B: BufRead>(bufread: B, name: Regex) {
let _ = TransformedStream {
source: bufread.lines(),
transf: move |line, f| {
let line = line.unwrap();
let repl = name.replace_all(&line, "XXX");
f(&repl as &str)
},
phantom: PhantomData,
};
}
error[E0597]: `line` does not live long enough
--> src/lib.rs:37:42
|
37 | let repl = name.replace_all(&line, "XXX");
| ^^^^ borrowed value does not live long enough
38 | f(&repl as &str)
39 | },
| - `line` dropped here while still borrowed
40 | phantom: PhantomData,
41 | };
| - borrowed value needs to live until here
error[E0597]: `repl` does not live long enough
--> src/lib.rs:38:16
|
38 | f(&repl as &str)
| ^^^^ borrowed value does not live long enough
39 | },
| - `repl` dropped here while still borrowed
40 | phantom: PhantomData,
41 | };
| - borrowed value needs to live until here
По моему мнению, line
и repl
живут достаточно долго, чтобы обрабатываться f
, как в версии &str
.Как и в приведенном выше примере итератора, использование repl.to_string()
удовлетворяет компилятору, но я не хочу клонировать каждую строку.
Моя интуиция заключается в том, что проблема заключается в PhantomData<T>
, который мне пришлосьдобавить в мою структуру, чтобы удовлетворить компилятор.Ограничивает ли оно время жизни T
(чтобы жить так же долго, как содержащую структуру), как если бы у меня было поле с типом T
?Я попытался заменить его на PhantomData<*const T>
, который, как я думал, не может ограничить срок службы, но это не решает мою проблему ...
Почему вторая версия не компилируется?Как я мог заставить это работать?