Flushing Output
Flushing output transfers unwritten bytes sitting in user-space buffers (BufWriter<W>, LineWriter<W>, Stdout) to the operating-system kernel (Write::flush()), and—for durable disk files—transfers dirty pages from the OS kernel page cache to physical non-volatile storage (File::sync_data() / File::sync_all()).
The Two Levels of Buffering: User Space vs. OS Kernel Page Cache
Understanding the boundary between Write::flush() and File::sync_all() is essential for both interactive CLI responsiveness and crash-safe file persistence:
Application Write Path:
[ Rust Application (`write!`) ]
|
v
+---------------------------------------------------+
| Level 1: User-Space RAM Buffer |
| (`BufWriter<File>`, `LineWriter`, `io::Stdout`) |
+---------------------------------------------------+
|
| <--- `writer.flush()?` pushes bytes across syscall boundary!
| (Note: For `std::fs::File` directly, `File::flush()` is a NO-OP
| because `File` has no user-space buffer!)
v
+---------------------------------------------------+
| Level 2: OS Kernel Buffer / Page Cache |
| (Terminal driver, TCP send buffer, FS page cache) |
+---------------------------------------------------+
|
| <--- `file.sync_data()?` / `file.sync_all()?` (`fsync`)
| flushes OS page cache to physical SSD/disk!
v
[ Physical Hardware (Disk / NVMe / Network Wire) ]
| Method | Target | What It Does |
|---|---|---|
io::stdout().flush() |
Stdout / LineWriter |
Forces partial lines (print!("Prompt: ") without \n) out to the terminal immediately |
buf_writer.flush() |
BufWriter<W> |
Empties BufWriter's 8 KiB user-space buffer into the inner writer W |
file.flush() |
std::fs::File |
No-op! (Ok(())) — File writes go straight to the OS kernel page cache on every .write() |
file.sync_data() |
std::fs::File |
OS fdatasync: flushes the file's data pages from the kernel page cache to physical disk |
file.sync_all() |
std::fs::File |
OS fsync: flushes both file data and filesystem metadata (size, timestamps) to physical disk |
Interactive CLI Prompt Pattern (print! + stdout().flush())
Because std::io::stdout() is line-buffered, calling print!("Enter port [8080]: "); without a trailing newline leaves the prompt text inside Stdout's buffer. If you immediately call io::stdin().read_line(&mut input) without flushing stdout() first, the user will see a blank cursor waiting for input before the prompt is printed!
use std::io::{self, BufRead, Cursor, Write};
fn prompt_with_flush(
prompt: &str,
mut out: impl Write,
mut input: impl BufRead,
) -> io::Result<String> {
write!(out, "{prompt}")?;
// Flush `out` BEFORE blocking on `input.read_line` so the prompt is visible:
out.flush()?;
let mut line = String::new();
input.read_line(&mut line)?;
Ok(line.trim().to_string())
}
fn main() -> io::Result<()> {
let mut terminal_out = Vec::new();
let simulated_keystrokes = Cursor::new("9000\n");
let answer = prompt_with_flush("Enter port [8080]: ", &mut terminal_out, simulated_keystrokes)?;
println!(
"prompt shown: {:?}, user entered: {:?}",
std::str::from_utf8(&terminal_out).unwrap(),
answer
);
Ok(())
}
prompt shown: "Enter port [8080]: ", user entered: "9000"