I don’t think Rust is old enough for everyone to agree what “idiomatic” is in this case. The major issue is that the D code has absolutely no error handling, whereas Rust doesn’t allow you to ignore it like that. So:
Standard disclaimer: it is not idiomatic to use unwrap
in Rust code where you know errors might happen. The only reason I’m doing it here is because the D code also doesn’t perform any error handling that doesn’t boil down to “explode”.
use std::fs::File;
use std::io::{BufRead, BufReader};
fn main() {
let word_count =
// Open file, discard errors, wrap in a buffer.
BufReader::new(File::open("file.txt").unwrap())
// Build line iterator from buffered file.
.lines()
// Discard IO errors caused during reading lines.
.map(Result::unwrap)
// Split each line into words and count them.
.map(|line| line.split_whitespace().count())
// Sum word counts.
.fold(0, |a, b| a + b);
println!("{}", word_count);
}
Also to note: the reason the “split” and “count” steps are a single step here is that separating them into two steps would require an intermediate allocation to “hold” the lines long enough for the split to be counted. It’s easier to just not make it two steps.
Here’s one that actually does error handling:
use std::fs::File;
use std::io::{self, BufRead, BufReader};
fn count_words() -> Result<(), io::Error> {
let line_counts =
// Open file, wrap in a buffer.
BufReader::new(try!(File::open("file.txt")))
// Build line iterator from buffered file.
.lines()
// Split each line into words and count them.
.map(|rl| rl.map(|l| l.split_whitespace().count()));
// Sum line counts, aborting on error.
let mut word_count = 0;
for count in line_counts {
word_count += try!(count);
}
println!("{}", word_count);
Ok(())
}
fn main() {
// Discard errors.
count_words().unwrap();
}
The reason this is so verbose is basically that there’s no way to combine fold
and try!
short of writing a custom try_fold
iterator adaptor or something, which is what I would do if I was writing a lot of code like this.
You can do the fold
with error handling inside of it, but this has the downside of continuing to try and process the input even after a failure. In this specific case, it should probably be fine, since I would expect the Lines
iterator to stop on an error, but in general (where each element of a sequence could potentially fail independently), it’s a bit of a pain. Here’s a version with error handling in the fold:
use std::fs::File;
use std::io::{self, BufRead, BufReader};
fn count_words() -> Result<(), io::Error> {
let word_count =
// Open file, wrap in a buffer.
BufReader::new(try!(File::open("file.txt")))
// Build line iterator from buffered file.
.lines()
// Split each line into words and count them.
.map(|rl| rl.map(|l| l.split_whitespace().count()))
// Sum line counts, propagating errors.
.fold(Ok(0), |a, b| a.and_then(|a| b.map(|b| a+b)));
println!("{}", try!(word_count));
Ok(())
}
fn main() {
// Discard errors.
count_words().unwrap();
}
Like I said: what counts as “idiomatic”, especially in a tiny example like this, isn’t entirely clear.