After reading the std::io::BufReader
docs, I'm not sure how to best pass a BufReader
between functions. Multiple permutations are allowed, but which is best?
I have a function that takes a file:
use std::{fs::File, io::BufReader};
fn read_some_data(f: &mut std::fs::File) {
let mut reader = BufReader::new(f);
read_some_other_data(&mut reader);
}
While this can be made to work, which permutation of reference access should be used when passing the reader around to other functions?
&mut BufReader<&mut File>
BufReader<&mut File>
&mut BufReader<File>
BufReader<File>
Since there is no need for each function to own the data I was thinking it would be best to pass as &mut BufReader<&mut File>
, but the example in the docs uses <File>
.
Whats a good rule of thumb to use here?
While this example uses BufReader
, I assume the same answer would apply to BufWriter
too.
The most idiomatic way is probably not to reference std::io::BufReader
at all. You actually want to refer to the traits Read
and/or BufRead
use std::io:BufRead;
// Could also take by move if needed
fn read_data<R: BufRead>(r: &mut R);
The function usually doesn't really care whether a reader is specifically the type std::io::BufReader
, merely that it has the same functionality.
This also gives you complete freedom to choose between BufReader<File>
, BufReader<&mut File>
or whichever other specialization you need. (It doesn't even have to be a file, which can help for testing!)
As for whether to use &mut
versus a move, generally in Rust it's standard to only request what you need. If you (and the functions you call) only require an immutable reference (&T
), use that, if you require mutability, use &mut T
.
Move is a bit more flexible, because while it can be used simply based on whether you need to use a function that takes something by value, it's also frequently used to assert that the function will "use up" the data in some way.
This is why BufReader
usually takes a File
and not a reference, and why most high-level "parse this file" IO functions tend to move by value. It's generally not the case that you consume part of a File
or reader with one adapter, and the rest with another.
In fact, this is conceptually so strong that rather than giving a reference, it's much more common to just move the File
into a higher-level reader and call a function like into_inner
to retrieve the file whenever you need to switch adapters.
Looking at the way BufReader
's methods are invoked (on &mut self
or self
) I would say you will usually want to pass &mut BufReader
, or BufReader
if the function needs to own the BufReader
in order to e.g. convert it into a Bytes
or Chars
iterator.
The reason why docs describe BufReader<File>
and not BufReader<&mut File>
is because the BufReader
owns its underlying Read
instance.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With