1

I have a clap App like this:

let m = App::new("test")
    .arg(
        Arg::with_name("INPUT")
            .help("a string to be frobbed")
            .multiple(true),
    )
    .get_matches();

I want to read the arguments as an iterable of strings if there are any myapp str1 str2 str3 but if not, to act as a filter and read an iterable of lines from stdin cat afile | myapp. This is my attempt:

let stdin = io::stdin();
let strings: Box<Iterator<Item = String>> = if m.is_present("INPUT") {
    Box::new(m.values_of("INPUT").unwrap().map(|ln| ln.to_string()))
} else {
    Box::new(stdin.lock().lines().map(|ln| ln.unwrap()))
};

for string in strings {
    frob(string)
}

I believe that, since I am just requiring the Iterator trait, a Box<Iterator<Item = String>> is the only way to go. Is that correct?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Scott Colby
  • 1,370
  • 12
  • 25
  • 2
    It is preferred to post separate questions instead of combining your questions into one. That way, it helps the people answering your question as well as others hunting for one of your questions. I have removed your second and third questions; focusing this about the question in your title. – Shepmaster Mar 13 '19 at 19:20

1 Answers1

2

There is rarely an "only way to go", and this case is no different. One alternative approach would be to use static dispatch instead of dynamic dispatch.

Your main processing code needs an iterator of strings as input. So you could define a processing function like this:

fn process<I: IntoIterator<Item = String>>(strings: I) {
    for string in strings {
        frob(string);
    }
}

The invocation of this code could look like this:

match m.values_of("INPUT") {
    Some(values) => process(values.map(|ln| ln.to_string())),
    None => process(io::stdin().lock().lines().map(|ln| ln.unwrap())),
}

The compiler will emit two different versions of process(), one for each iterator type. Each version statically calls the iterator functions it is compiled for, and there is only a single dispatch to the right function in the match statement.

(I probably got some details wrong here, but you get the idea.)

Your version, on the other hand, uses the type Box<dyn Iterator<Item = String>>, so the iterators will be allocated on the heap, and there will be a dynamic dispatch each time next() is called on the iterator. Which is probably fine.

There are certainly more ways of structuring the code and dispatching between the two different kinds of input, e.g. using the Either type from the either crate, or simply writing two different for loops for the two cases. Which one to choose depends on tradeoffs with other requirements of your code, your performance requirements and your personal preferences.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • And there's [the possibility of dynamic dispatch without the heap](https://stackoverflow.com/a/28220053/155423), as well. – Shepmaster Mar 13 '19 at 20:48
  • 1
    You could also do something like `fn process(strings: I) where I: IntoIterator, I::Item: AsRef` to avoid the forced allocation of the `String`. – Shepmaster Mar 13 '19 at 20:50
  • @Shepmaster Yeah, I realized when I finished writing this answer that I don't really know what the question is, but since I had already typed this, I posted it anyway. There are lots of things you _could_ do, but we don't really know what problem the OP is trying to solve. :) – Sven Marnach Mar 13 '19 at 20:54
  • I think I really stepped into quite a complicated question here...the `either` crate looks very useful with the ability to forward the iterator. Overall, I'm not sure what is "best" for my use case is--it's just something I'm writing for fun and the average invocation already has a time of `0.00` so I'm clearly not hitting any performance limitations. Thanks for showing me the static dispatch alternative. – Scott Colby Mar 16 '19 at 03:43
  • Besides reasoning from knowledge of the language, do you know how to determine if static or dynamic dispatch is being done? For instance, if I end up using the `either` crate, can I look at my binary and figure out which it is using? – Scott Colby Mar 16 '19 at 03:52
  • 1
    @ScottColby If you are writing the code for fun, I recommend using the most fun approach. :) Rust generally uses static dispatch, and only uses dynamic dispatch for trait objects, i.e. types like `&dyn Trait` or `Box`, written `&Trait` or `Box` in earlier versions of Rust. However, you only really need to care about this if you run into performance problems, in which case you need to profile your code and figure out where the bottlenecks are. In this case, for example, it's likely that the optimiser will be able to pull the lookup for the call to `next()` out of the loop. – Sven Marnach Mar 16 '19 at 17:02