If you like to use iterator, you will at one point try to write the following code:
fn create_iterator() -> impl Iterator<Item=Foo> { /* ... */ }
fn generate_iterator() -> impl Iterator<Item=Foo> { /* ... */ }
fn create_or_generate(condition: bool) -> impl Iterator<Iter=Foo> {
if condition {
create_iterator()
} else {
generate_iterator()
}
}
The intent of the code is perfectly clear by unfortunately the compiler will reject it:
error[E0308]: `if` and `else` have incompatible types
--> src/lib.rs:9:9
|
3 | fn generate_iterator() -> impl Iterator<Item=Foo> { ... }
| ----------------------- the found opaque type
...
6 | / if condition {
7 | | create_iterator()
| | ----------------- expected because of this
8 | | } else {
9 | | generate_iterator()
| | ^^^^^^^^^^^^^^^^^^^ expected opaque type, found a different opaque type
10 | | }
| |_____- `if` and `else` have incompatible types
|
= note: expected type `impl std::iter::Iterator` (opaque type at <src/lib.rs:2:25>)
found opaque type `impl std::iter::Iterator` (opaque type at <src/lib.rs:3:27>)
= note: distinct uses of `impl Trait` result in different opaque types
A solution would be to slightly modify the code like this:
fn create_or_generate(condition: bool) -> impl Iterator<Item=Foo> {
if condition {
Iter::T1(create_iterator())
} else {
Iter::T2(generate_iterator())
}
}
enum Iter<T1, T2> {
T1(T1),
T2(T2),
}
impl<T1, T2> Iterator for Iter<T1, T2>
where
T1: Iterator<Item=Foo>,
T2: Iterator<Item=Foo>,
{
type Item=Foo;
fn next(&mut self) -> Option<Self::Item> {
match self {
Iter::T1(t1) => t1.next(),
Iter::T2(t2) => t2.next(),
}
}
}
This code is very verbose, and desn't add value for the reader.
Instead, I would propose make the initial snipped valid Rust. The compiler would (if needed) auto-generate an union that implements SomeTrait
when a function returns impl SomeTrait
and there are two or more types implementing SomeTrait
returned by the function.
In term of design, I think that no markers (like the Iter::T1(...)
that I used in the example above, a potential .into()
, or a new keyword) are needed when converting from one concrete type into the compiler-generated union. Indeed, it's obvious that a conversion is needed (otherwise the could would not compile), and both the source and destination of the conversion are known and ambiguous. Implicit conversion is already a mechanism that exists in Rust (with the ?
operator), and I think we can have them here since they can't introduce ambiguities.
Before formalizing this idea in a proper RFC, I wanted to gather feedback. I am also willing (if this idea is accepted) to implement it, but I will probably need some mentoring.