Iterators Make Sense in Rust
Interface Iterator<E>
has been in the Java standard library since version 1.2, December 8, 1998. When I first started using them, they were useful, but not 'mind blowing' useful. In Java 8, March 18, 2014, Streams were introduced, which were much better iterators, and could easily support parallel operations.
However, long before this, Scala collections supported operations such as map, flatmap, filter, reduce, etc., which were basically a more useful abstraction on iterators, without explicitly instantiating an iterator. Eventually, there was the Scala View, which basically turned a Scala collection into something like a Java Stream, a machine that does lazy evaluation of the collection, until the View is explicitly collected. This was a performance enhancement, where the API could optimize a collection of operations on a collection of items. See also the Joys of Monads and Monoids.
Sadly, Java Streams were confusing to me at first because I was already using Scala/Akka Streams, a very different kind of beast. In Java, a Flow is like a Scala Stream.
There are only two hard things in Computer Science: cache invalidation and naming things. -- Phil Karlton
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
In Rust, Iterator does not look much different from Java, but, unlike Java, Rust iterators are lazy like Java Streams, or Scala Views. However, the nice thing is, IMHO, is that they are named properly. Often in Rust, things are named much better than other languages.
While Scala does, by default, give the ability to iterate over a collection without being lazy, in most cases it is better to be lazy, and only collect the result once all the components of the iteration are understood and can be optimized.
领英推荐
Also, in Rust, it is easy to iterate imperatively or functionally, whatever makes most readable code at the time. For example,
// print all directory entries that are files.
for entry in read_dir("./")?.filter_map(|e| e.ok()) {
let entry: String = W(&entry).try_into()?;
println!("{entry}");
}
Where 'for' ... 'in' ... is imperative style.
Where read_dir("./")?.filter_map(|e| e.ok()) is functional style.
Java, in 1995, was a refinement over C and C++, where it was not as easy to shoot yourself in the foot as it was with C++.
Eventually came Groovy, which in many ways was a better Java, and then Scala, which was a way better Groovy, and ultimately Kotlin, which had many of the strengths of Scala, but made it harder to shoot yourself in the foot.
However, the problem with C, C++, Java, Groovy, Kotlin, etc. is two epic language design defects:
When dealing with Iterators in Rust, there are no nulls to deal with, and there are no exceptions to deal with either. Also, Rust is astonishingly superior than Kotlin at making sure you don't shoot yourself in the foot. On the other hand, Rust often has the functional flavour and feel of Scala and Kotlin.