Создать метод на итераторе, который возвращает итератор в Rust - PullRequest
3 голосов
/ 05 марта 2020

Я хочу определить ленивый square() метод без лишних издержек времени выполнения (без ключевого слова dyn), который может быть вызван для любого Iterable<Item = u8> и возвращает еще один Iterable<Item = u8>, например:

fn main() {
    vec![1, 2, 3, 4, 5]
        .iter()
        .filter(|x| x > 1)
        .squared()
        .filter(|x| x < 20);
}

Я знаю, как определить squared() как отдельную функцию:

fn squared<I: Iterator<Item = u8>>(iter: I) -> impl Iterator<Item = u8> {
    iter.map(|x| x * x)
}

Чтобы определить этот метод на Iterator<Item = u8>, я должен сначала определить trait. Вот где я борюсь - черты не могут использовать ключевое слово impl в возвращаемых значениях.

Я ищу что-то вроде следующего, что не работает:

trait Squarable<I: Iterator<Item = u8>> {
    fn squared(self) -> I;
}

impl<I, J> Squarable<I> for J
where
    I: Iterator<Item = u8>,
    J: Iterator<Item = u8>,
{
    fn squared(self) -> I {
        self.map(|x| x * x)
    }
}

У меня было много неудачные попытки решить проблему, включая изменение типа возврата squared на Map<u8, fn(u8) -> u8> и работу с IntoIterable s, но пока ничего не получалось. Любая помощь будет принята с благодарностью!

1 Ответ

6 голосов
/ 06 марта 2020

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

trait Squarable {
    type Output: Iterator<Item = u8>;
    fn squared(self) -> I;
}

При этом существует несколько различных подходов к решению этой проблемы, каждый из которых имеет свои преимущества и недостатки.

Использование объектов признаков

Первый заключается в использовании объектов-черт, например dyn Iterator<Item = u8>, для удаления типа во время выполнения. Это требует небольших затрат времени выполнения, но, безусловно, является самым простым решением в стабильном Rust на сегодняшний день:

trait Squarable {
    fn squared(self) -> Box<dyn Iterator<Item = u8>>;
}

impl<I: 'static + Iterator<Item = u8>> Squarable for I {
    fn squared(self) -> Box<dyn Iterator<Item = u8>> {
        Box::new(self.map(|x| x * x))
    }
}

Использование настраиваемого типа итератора

В стабильной ржавчине это определенно самый чистый из с точки зрения пользователя черты, однако для его реализации требуется немного больше кода, потому что вам нужно написать собственный тип итератора. Однако для простого итератора map это довольно просто:

trait Squarable: Sized {
    fn squared(self) -> SquaredIter<Self>;
}

impl<I: Iterator<Item = u8>> Squarable for I {
    fn squared(self) -> SquaredIter<I> {
        SquaredIter(self)
    }
}

struct SquaredIter<I>(I);

impl<I: Iterator<Item = u8>> Iterator for SquaredIter<I> {
    type Item = u8;
    fn next(&mut self) -> Option<u8> {
        self.0.next().map(|x| x * x)
    }
}

Использование явного Map type

<I as Iterator>::map(f) имеет тип std::iter::Map<I, F>, поэтому если Тип F функции отображения известен, мы можем использовать этот тип явно, без затрат времени выполнения. Тем не менее, в нем указывается указанный тип c как часть типа возвращаемого значения функции, что затрудняет его замену в будущем без нарушения зависимого кода. В большинстве случаев функция также не будет известна; в этом случае мы можем использовать F = fn(u8) -> u8, однако, так как функция не сохраняет никакого внутреннего состояния (но часто это не работает).

trait Squarable: Sized {
    fn squared(self) -> std::iter::Map<Self, fn(u8) -> u8>;
}

impl<I: Iterator<Item = u8>> Squarable for I {
    fn squared(self) -> std::iter::Map<Self, fn(u8) -> u8> {
        self.map(|x| x * x)
    }
}

Используя связанный тип

Альтернативный к вышесказанному - придать признаку ассоциированный тип. Это по-прежнему имеет ограничение на то, что тип функции должен быть известен, но он немного более общий, поскольку тип Map<...> связан с реализацией, а не с самой чертой.

trait Squarable {
    type Output: Iterator<Item = u8>;
    fn squared(self) -> Self::Output;
}

impl<I: Iterator<Item = u8>> Squarable for I {
    type Output = std::iter::Map<Self, fn(u8) -> u8>;
    fn squared(self) -> Self::Output {
        self.map(|x| x * x)
    }
}

Использование impl в связанный тип

Это похоже на приведенное выше «Использование связанного типа», но вы можете полностью скрыть фактический тип, за исключением того факта, что это итератор. Лично я считаю, что это предпочтительное решение, но, к сожалению, оно все еще нестабильно (зависит от функции type_alias_impl_trait), поэтому вы можете использовать его только в ночной Rust.

#![feature(type_alias_impl_trait)]

trait Squarable {
    type Output: Iterator<Item = u8>;
    fn squared(self) -> Self::Output;
}

impl<I: Iterator<Item = u8>> Squarable for I {
    type Output = impl Iterator<Item = u8>;
    fn squared(self) -> Self::Output {
        self.map(|x| x * x)
    }
}
...