I/O
Locking
Rust’s print!
and println!
macros lock stdout on every call. If you
have repeated calls to these macros it may be better to lock stdout manually.
For example, change this code:
#![allow(unused)] fn main() { let lines = vec!["one", "two", "three"]; for line in lines { println!("{}", line); } }
to this:
#![allow(unused)] fn main() { fn blah() -> Result<(), std::io::Error> { let lines = vec!["one", "two", "three"]; use std::io::Write; let mut stdout = std::io::stdout(); let mut lock = stdout.lock(); for line in lines { writeln!(lock, "{}", line)?; } // stdout is unlocked when `lock` is dropped Ok(()) } }
stdin and stderr can likewise be locked when doing repeated operations on them.
Buffering
Rust file I/O is unbuffered by default. If you have many small and repeated
read or write calls to a file or network socket, use BufReader
or
BufWriter
. They maintain an in-memory buffer for input and output,
minimizing the number of system calls required.
For example, change this unbuffered writer code:
#![allow(unused)] fn main() { fn blah() -> Result<(), std::io::Error> { let lines = vec!["one", "two", "three"]; use std::io::Write; let mut out = std::fs::File::create("test.txt")?; for line in lines { writeln!(out, "{}", line)?; } Ok(()) } }
to this:
#![allow(unused)] fn main() { fn blah() -> Result<(), std::io::Error> { let lines = vec!["one", "two", "three"]; use std::io::{BufWriter, Write}; let mut out = BufWriter::new(std::fs::File::create("test.txt")?); for line in lines { writeln!(out, "{}", line)?; } out.flush()?; Ok(()) } }
The explicit call to flush
is not strictly necessary, as flushing will
happen automatically when out
is dropped. However, in that case any error
that occurs on flushing will be ignored, whereas an explicit flush will make
that error explicit.
Forgetting to buffer is more common when writing. Both unbuffered and buffered
writers implement the Write
trait, which means the code for writing
to an unbuffered writer and a buffered writer is much the same. In contrast,
unbuffered readers implement the Read
trait but buffered readers implement
the BufRead
trait, which means the code for reading from an unbuffered reader
and a buffered reader is different. For example, it is difficult to read a file
line by line with an unbuffered reader, but it is trivial with a buffered
reader by using BufRead::read_line
or BufRead::lines
. For this reason,
it is hard to write an example for readers like the one above for writers,
where the before and after versions are so similar.
Finally, note that buffering also works with stdout, so you might want to combine manual locking and buffering when making many writes to stdout.
Reading Lines from a File
This section explains how to avoid excessive allocations when using
BufRead
to read a file one line at a time.
Reading Input as Raw Bytes
The built-in String type uses UTF-8 internally, which adds a small, but
nonzero overhead caused by UTF-8 validation when you read input into it. If you
just want to process input bytes without worrying about UTF-8 (for example if
you handle ASCII text), you can use BufRead::read_until
.
There are also dedicated crates for reading byte-oriented lines of data and working with byte strings.