Using Map as an Iterator interchangeably with Vector iterator

Viewed 85

A simple program, what I am trying to do is get lines from a file, or if the file is not present, pass in a zero-length iterator:

use std::fs::File;
use std::io::{self, BufRead};

fn run_against_input(inp: &mut dyn Iterator<Item = String>) {
    for i in inp {
        println!("Input line: {}", i);
    }
}

fn main() {
    let file = File::open("data.txt");
    let input: dyn Iterator<Item = String> = match file {
        Ok(f) => io::BufReader::new(f).lines()
            .map(|line| line.unwrap()),

        Err(_) => Vec::new().into_iter()
    };
    
    run_against_input(&mut dyn input);
}

When I do this, I get the following error:

   Compiling playground v0.0.1 (/playground)
error: expected expression, found keyword `dyn`
  --> src/main.rs:19:28
   |
19 |     run_against_input(&mut dyn input);
   |                            ^^^ expected expression

error[E0308]: mismatched types
  --> src/main.rs:13:18
   |
13 |           Ok(f) => io::BufReader::new(f).lines()
   |  __________________^
14 | |             .map(|line| line.unwrap()),
   | |______________________________________^ expected trait object `dyn Iterator`, found struct `Map`
   |
   = note: expected trait object `dyn Iterator<Item = String>`
                    found struct `Map<std::io::Lines<BufReader<File>>, [closure@src/main.rs:14:18: 14:38]>`

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

I do specifically need a &mut reference to the Iterator in the code I am writing (this is a small reproduction of the problem that I am facing), but I guess that has nothing to do with the issue at hand. I see that there is an impl on Iterator for Map, but the error I get is that this is not the trait object for Iterator<Item = String>. I also tried this:

let input: &mut dyn Iterator<Item = String> = match file {
    Ok(f) => &mut io::BufReader::new(f).lines()
        .map(|line| line.unwrap()),

    Err(_) => &mut Vec::new().into_iter()
};

which of course didn't work, since the temporary value is being dropped in the statement to which I am returning a reference (which is why I did the let binding thing as the compiler suggested).

EDIT Link to playground - https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=99b8105ecc266e03db2d92cc22610962

2 Answers

You can hold the iterators for each branch in a separate variable outside the match (and also use std::iter::empty() instead of Vec::new().into_iter()):

use std::fs::File;
use std::io::{self, BufRead};
use std::iter;

fn run_against_input(inp: impl Iterator<Item = String>) {
    for i in inp {
        println!("Input line: {}", i);
    }
}

fn main() {
    let file = File::open("data.txt");
    let mut lines;
    let mut no_lines;
    let input: &mut dyn Iterator<Item = String> = match file {
        Ok(f) => {
            lines = io::BufReader::new(f).lines().map(Result::unwrap);
            &mut lines
        },
        Err(_) => {
            no_lines = iter::empty();
            &mut no_lines
        },
    };
    
    run_against_input(input);
}

You can use a Box instead of a reference, so the value is owned and won't be dropped:

let mut input: Box<dyn Iterator<Item = String>> = match file {
        Ok(f) => Box::new(io::BufReader::new(f).lines().map(|line| line.unwrap())),
        Err(_) => Box::new(Vec::new().into_iter()),
    };

playground

If you prefer you could also change your function to take a Box instead of a reference:

fn run_against_input(inp: Box<dyn Iterator<Item = String>>) {
Related