Conditional assignment: cannot determine common trait

Hi everyone,

I know similar issues exist (see this one), but I cannot find a way to solve mine.

My problem is that I cannot find a common object that would make this compile. You have a MWE just below. In general, I want to specify that my return value is always going to be some iterator of a known (and fixed) item type.

Thanks in advance !

fn main() {
    let a = true;
    let x = vec![1, 2, 3, 4].into_iter();
    
    let x = x.filter(|e| *e > 1);
    
    let x = if a {
        x.filter(|e| true) // as ?
    } else {
        x // as ?
    };
    
    for e in x {
        println!("{}", e);
    }
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0308]: `if` and `else` have incompatible types
  --> src/main.rs:10:9
   |
5  |       let x = x.filter(|e| *e > 1);
   |                        ---------- the found closure
6  |       
7  |       let x = if a {
   |  _____________-
8  | |         x.filter(|e| true) // as ?
   | |         ------------------ expected because of this
9  | |     } else {
10 | |         x // a ?
   | |         ^ expected struct `Filter`, found struct `std::vec::IntoIter`
11 | |     };
   | |_____- `if` and `else` have incompatible types
   |
   = note: expected type `Filter<Filter<std::vec::IntoIter<_>, [closure@src/main.rs:5:22: 5:32]>, [closure@src/main.rs:8:18: 8:26]>`
            found struct `Filter<std::vec::IntoIter<_>, [closure@src/main.rs:5:22: 5:32]>`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` due to previous error

The reason you are running into trouble is that traits are not types. You can't just say that x is an Iterator. The type of x has to be, well, a type.

The first option here is to use the thing called a trait object. This is what you get when you put dyn in front of the name of a trait, and the trait object is a type. However, trait objects can only exist behind references or smart pointers such as Box, so you have to wrap them in a Box to use that feature.

fn main() {
    let a = true;
    let x = vec![1, 2, 3, 4].into_iter();
    
    let x = x.filter(|e| *e > 1);
    
    let x = if a {
        Box::new(x.filter(|e| true)) as Box<dyn Iterator<Item = i32>>
    } else {
        Box::new(x) as Box<dyn Iterator<Item = i32>>
    };
    
    for e in x {
        println!("{}", e);
    }
}

However the disadvantage of this is that this allocates memory and it has a runtime cost. There's another option, which is to use the Either type from the itertools crate. This works because Either is an enum with two options, allowing you to merge two types into one.

use itertools::Either;

fn main() {
    let a = true;
    let x = vec![1, 2, 3, 4].into_iter();
    
    let x = x.filter(|e| *e > 1);
    
    let x = if a {
        Either::Left(x.filter(|e| true))
    } else {
        Either::Right(x)
    };
    
    for e in x {
        println!("{}", e);
    }
}
4 Likes

Thank you for the very detailed reply ! Your solution with Either is also what I was looking for, perfect :slight_smile:

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.