Read and Write¶
Level: 201 · working knowledge
One line: Two traits stand between your code and every byte source there is — a file, a socket, stdin, a Vec<u8> — which is why a function typed fn load(r: impl Read) can be tested against an in-memory string and shipped against a file, with no seam between the two.
Stub — an outline, not a lesson. There is no runnable example behind this page yet, so nothing on it has been through the check that backs every other claim in this library. The bullets below are the questions the finished page has to answer.
What it has to cover¶
- The two required methods (
read,write) and the large provided surface built on them —read_to_string,read_to_end,write_all,lines,bytes &[u8]implementsReadandVec<u8>implementsWrite, which is the whole testing story: no temp file, no mock, no trait of your own- Taking
impl Read/&mut impl Writeas a parameter instead of namingFile— the single change that makes an I/O function testable BufReadand whyBufReaderexists: areadis a syscall, and reading a line at a time without buffering is one syscall per bytewrite!andwriteln!against aWrite, and how they differ fromprint!- The short read:
writemay write fewer bytes than you gave it andreadmay return fewer than you asked for.write_allandread_exactare the loops you would otherwise write std::io::Writeversusstd::fmt::Write— two traits, same method name, different error type, and the import error that follows- Flushing, and what
Dropon aBufWriterdoes and does not promise about errors
The trap it exists for¶
A BufWriter flushes on drop and discards the error if that flush fails — there is nowhere for a Drop to return it. A program that writes a report and never calls flush() explicitly can exit 0 having written a truncated file. The fix is one line, and it is the reason flush() returns a Result at all.
See also¶
- Readers are fallible — the
io::Errorhalf, and the loop that keeps going - Reading lines efficiently —
BufRead::linesand what it allocates - Opening a file — the
Filethese traits abstract over - Standard error, and exit status — the two writers a program has by default
- Temporary directories in tests — what you need less of once a function takes
impl Read std::io::Read↗ · Comprehensive Rust:ReadandWrite↗
Po polsku¶
Sedno Read i Write jest takie, że to cechy (traits), a nie typy: &[u8] implementuje Read, a Vec<u8> implementuje Write, więc funkcja przyjmująca impl Read daje się przetestować na danych trzymanych w pamięci i uruchomić na pliku bez żadnej różnicy w kodzie. Polskie materiały mówią zwykle o „strumieniach wejścia/wyjścia”, co podsuwa złą intuicję — tutaj nie ma klasy strumienia ani hierarchii dziedziczenia, są dwie cechy z jedną wymaganą metodą każda (read, write) i całą wygodną resztą (read_to_string, write_all, lines) dobudowaną na nich jako metody domyślne.
Pułapka, dla której ta strona istnieje, jest cicha: BufWriter opróżnia bufor przy wypuszczeniu zasobu (drop) i gubi błąd, jeśli to opróżnienie się nie powiedzie — Drop nie ma jak zwrócić Result. Program, który zapisze raport i nigdy nie wywoła jawnie flush(), potrafi zakończyć się kodem wyjścia 0, zostawiając ucięty plik. Druga rzecz do zapamiętania to write z krótkim zapisem — wolno mu zapisać mniej bajtów, niż mu podano — oraz to, że std::io::Write i std::fmt::Write są dwiema różnymi cechami o tych samych nazwach metod, więc błąd o brakującym imporcie potrafi wskazywać nie tę, o którą chodziło.
Szukaj po polsku: strumienie wejścia/wyjścia w Ruscie · buforowanie zapisu · rust BufWriter flush on drop · rust impl Read for testing · rust io::Write vs fmt::Write