Как вернуть итератор с общим типом, связанным с функцией?

#generics #rust #iterator #traits #parametric-polymorphism

#общие #Ржавчина #итератор #Трейты #параметрический-полиморфизм

Вопрос:

Для такой функции, как эта:

 fn generate_even(a: i32, b: i32) -> impl Iterator<Item = i32> {
    (a..b).filter(|x| x % 2 == 0)
}
 

Я хочу сделать его универсальным, вместо конкретного типа i32 я хочу иметь любой тип, который доступен в диапазоне, и обеспечить реализацию фильтра.

Безуспешно пытался использовать приведенную ниже формулу:

 fn generate_even(a: T, b: T) -> impl Iterator<Item = T>
    where T: // range   filter   what to put here?
{
    (a..b).filter(|x| x % 2 == 0)
}
 

Как можно реализовать что-то подобное?

Комментарии:

1. Наиболее ограничительной частью является не диапазон, который можно просто ограничить where std::ops::Range<T>: Iterator , или фильтр, который реализован для всех итераторов, а x % 2 == 0 , который требует его использования для чисел, что может потребовать чего-то вроде num_traits .

2. Вы можете привязать T std::ops::Rem , чтобы разрешить оператор modula. Для каждого оператора есть черта.

3. @PitaJ Но тогда какой тип будет принимать RHS?

4. @PitaJ это будет тот же тип, что и a

Ответ №1:

 use std::ops::Range;
use std::ops::Rem;
use std::cmp::PartialEq;

fn generate_even<T>(a: T, b: T) -> impl Iterator<Item = T>
  where
    Range<T>: Iterator<Item = T>,
    T: Copy   Rem<Output = T>   From<u8>   PartialEq
{
    let zero: T = 0_u8.into();
    let two: T = 2_u8.into();
    (a..b).filter(move |amp;x| x % two == zero)
}

fn main() {
    let even_u8s = generate_even(0_u8, 11_u8);
    let even_i16s = generate_even(0_i16, 11_i16);
    let even_u16s = generate_even(0_u16, 11_u16);
    // and so on
    let even_u128s = generate_even(0_u128, 11_u128);
}
 

игровая площадка

Самая сложная часть решения — это x % 2 == 0 универсальная реализация, потому что по умолчанию Rust интерпретирует целочисленные литералы как i32 s, но вы хотите, чтобы ваша функция была универсальной для всех возможных целочисленных типов, что означает, что вы должны создать 2 0 значение и любого целочисленного типа, указанного вызывающим, и самый простой способ сделать этопривязка T к From<u8> которому позволяет нам преобразовывать любое значение из 0 в 256 в любой целочисленный тип (за i8 единственным исключением). Приведенное выше решение является общим и работает для всех целочисленных типов, кроме i8 .

Ответ №2:

На самом деле можно поддерживать все целочисленные типы, в том числе i8 , используя TryInto .

 use std::ops::Range;
use std::ops::Rem;
use std::cmp::PartialEq;
use std::fmt;
use std::convert::{TryFrom, TryInto};

fn generate_even<T>(a: T, b: T) -> impl Iterator<Item = T>
  where
    Range<T>: Iterator<Item = T>,
    T: Copy   Rem<Output = T>   TryFrom<u8>   PartialEq   fmt::Debug,
    <T as TryFrom<u8>>::Error: fmt::Debug
{
    let zero: T = 0_u8.try_into().unwrap();
    let two: T = 2_u8.try_into().unwrap();
    (a..b).filter(move |amp;x| x % two == zero)
}

fn main() {
    let even_u8s = generate_even(0_i8, 11_i8);
    let even_u8s = generate_even(0_u8, 11_u8);
    let even_i16s = generate_even(0_i16, 11_i16);
    let even_u16s = generate_even(0_u16, 11_u16);
    // and so on
    let even_u128s = generate_even(0_u128, 11_u128);
}