Let's say I have some way of transforming a char
using some lookup structure.
struct LookUp(Vec<char>);
impl LookUp {
fn from(s: &str) -> LookUp {
LookUp(s.chars().map(|c| c.to_ascii_lowercase()).collect())
}
}
fn transform_char(c: char, look_up: &LookUp) -> char {
if look_up.0.contains(&c.to_ascii_lowercase()) {
c.to_ascii_uppercase()
} else {
c
}
}
I want to use it to transform an Iterator<Item = char>
into another Iterator<Item = char>
, composing already existing adaptors. Of course I can do it with functions (here are two version, one with more implicit impl Trait
and the second more explicit)
fn transform<'a>(
input: impl Iterator<Item = char> + 'a,
look_up: &'a LookUp,
) -> impl Iterator<Item = char> + 'a {
input.map(move |c| transform_char(c, look_up))
}
fn explicit_transform<'a, I>(
input: I,
look_up: &'a LookUp,
) -> std::iter::Map<I, impl FnMut(char) -> char + 'a>
where
I: Iterator<Item = char>,
{
input.map(move |c| transform_char(c, look_up))
}
but then using them is a bit annoying because I cannot chain the methods chars
, transform
and collect
(imagine the annoyance in a more involved situation):
fn main() {
let input = "Rust is fantastic!";
let look_up = LookUp::from("rust");
let output: String = transform(input.chars(), &look_up).collect();
println!("{}", output);
let output: String = explicit_transform(input.chars(), &look_up).collect();
println!("{}", output);
}
What I would like to do is to add a method .transform(look_up)
to every Iterator<Item = char>
. One possible way of doing this is with
trait Transform: Sized {
fn transform<'a>(
self,
look_up: &'a LookUp,
) -> std::iter::Map<Self, Box<dyn FnMut(char) -> char + 'a>>;
}
impl<I> Transform for I
where
I: Iterator<Item = char>,
{
fn transform<'a>(
self,
look_up: &'a LookUp,
) -> std::iter::Map<Self, Box<dyn FnMut(char) -> char + 'a>> {
self.map(Box::new(move |c| transform_char(c, look_up)))
}
}
and the usage becomes much more comfortable because I can now chain the methods.
fn main() {
let output: String = input.chars().transform(&look_up).collect();
println!("{}", output);
}
The downside is that I have to use dynamic allocation with Box<dyn Trait>
because impl Trait
is not permitted inside traits.
Here is a link to the code so far.
The code I would like to write is more similar to this
trait Transform: Sized {
fn transform<'a>(
self,
look_up: &'a LookUp,
) -> std::iter::Map<Self, impl FnMut(char) -> char + 'a>;
}
impl<I> Transform for I
where
I: Iterator<Item = char>,
{
fn transform<'a>(
self,
look_up: &'a LookUp,
) -> std::iter::Map<Self, Box<dyn FnMut(char) -> char + 'a>> {
self.map(move |c| transform_char(c, look_up))
}
}
where I'm forced to use impl FnMut(char) -> char
because I cannot write down the explicit type of the closure move |c| transform_char(c, look_up)
. Unfortunately, this is not currently possible. It's not even possible to solve the problem with an associated type (as far as I can see).
The last alternative is to use the strategy adopted by Itertools. In that case, every iterator adaptor returns a new structure specifically designed for the task. The problem I have with that is the proliferation of unnecessary Iterator
structures, where my resulting iterator could be instead expressed as a combination of already existing Map
, Filter
, etc... Also this results in much more code, because I have to replicate the logic of Map
, Filter
, etc. instead of simply returning them.
What is your recommendation in this regard?