Могу ли я сделать эту структуру более общей? - PullRequest
0 голосов
/ 14 сентября 2018

Детская площадка

Контекст

Я хочу предварительно обработать строки текстового файла перед передачей их в функцию 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>, который, как я думал, не может ограничить срок службы, но это не решает мою проблему ...

Почему вторая версия не компилируется?Как я мог заставить это работать?

1 Ответ

0 голосов
/ 14 сентября 2018

Вместо T используйте &T.Это более прямой перевод оригинальной версии &str, поэтому вы можете быть более уверены, что она будет работать после внесения изменений.И это делает:

pub struct TransformedStream<S, FT, T>
where
    S: Iterator,
    T: ?Sized,
    FT: FnMut(S::Item, &mut FnMut(&T)),
{
    source: S,
    transf: FT,
    phantom: PhantomData<*const T>,
}

impl<S, FT, T> TransformedStream<S, FT, T>
where
    S: Iterator,
    T: ?Sized,
    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));
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...