I'm very new to rust, so I concede off the bat that there may be a better third way to do this, but I'm very interested in what someone more experienced with the language has to say.
Given a generic function with signature:
fn process_lines<T: BufRead + Sized>(reader: T, re: Regex) {
What is the more "conventional" rust approach to calling this?
There is the straight forward approach:
match input {
"-" => {
let stdin = io::stdin();
process_lines(stdin.lock(), re)
}
_ => {
let f = File::open(input).unwrap();
process_lines(BufReader::new(f), re)
}
};
In other languages I would avoid writing out function calls multiple times by doing something that I think would translate as this:
let reader: Box<dyn BufRead> = match input {
"-" => {
let stdin = io::stdin();
Box::new(stdin.lock())
}
_ => {
let f = File::open(input).unwrap();
Box::new(BufReader::new(f))
}
};
process_lines(reader, re);
Is there a better way to construct reader
other than calling Box ?
This is a super trivial situation, but I'm worried I am falling into some kind of mental trap doing things the way I'm accustomed to...
I understand there is a some performance penalty for using Box... but I would imagine this is more of a problem inside some sort of nested control flow.
Which of these patterns scales better in large rust code bases, or it is always a per case kind of call?
Any and all insights on this dichotomy (or false dichotomy if there is indeed a third way) are really appreciated!
Temporary lifetime extension has been landed in rust 1.79, which makes it possible to write this:
let reader: &mut dyn BufRead = match input {
"-" => &mut std::io::stdin().lock(),
_ => {
let file = std::fs::File::open(input).unwrap();
&mut std::io::BufReader::new(file)
}
};
process_lines(reader, re);
Temporary values that are generated in if
, match
, and raw blocks, with borrows that escape the scope of the block, would have extended lifetime to match the borrows' lifetime.
This is useful for many dynamic dispatch cases, which are not supported in the earlier versions.