File System Operations

write()

In Rust file I/O, write refers to three distinct APIs with important behavioral differences:

  1. std::fs::write(path, contents) -> io::Result<()>: High-level convenience function that creates (or truncates) path, writes all bytes of contents (C: AsRef<[u8]>), and closes the file.
  2. Write::write(&mut self, buf: &[u8]) -> io::Result<usize>: Low-level trait method that writes at most buf.len() bytes in a single system call and returns the number of bytes actually written (may be less than buf.len()!).
  3. Write::write_all(&mut self, buf: &[u8]) -> io::Result<()>: Trait method that loops calling .write() until every byte of buf has been written.

Comparing fs::write, Write::write, Write::write_all, and write!

Which Write API Should You Call?
  |
  +-- Have a file path & the full `[u8]` / `&str` ready in memory?
  |     +---> `std::fs::write(path, data)?` (1 line: opens, writes all, closes)
  |
  +-- Writing a `[u8]` slice to an already-open `File` / `BufWriter` / `TcpStream`?
  |     +---> `stream.write_all(bytes)?` (Handles short writes & EINTR automatically!)
  |
  +-- Writing formatted text (`"{id}: {name}"`) to an open stream?
  |     +---> `write!(stream, "{id}: {name}")?` or `writeln!(stream, ...)?`
  |
  +-- Implementing a custom `Write` adapter or low-level nonblocking loop?
        +---> `stream.write(bytes)?` (Inspect returned `usize` count of written bytes!)
API Accepts Handles Short Writes? Returns
std::fs::write(path, data) path: impl AsRef<Path>, data: impl AsRef<[u8]> Yes (calls write_all internally) io::Result<()>
stream.write_all(bytes) bytes: &[u8] Yes io::Result<()>
write!(stream, "fmt") Format string + args Yes (calls write_all per segment) io::Result<()>
stream.write(bytes) bytes: &[u8] No! (Caller must check returned usize!) io::Result<usize>

Demonstrating fs::write, write_all, and Vectored Writes (write_vectored)

In addition to write_all, std::fs::File and sockets support vectored I/O (write_vectored(&[IoSlice::new(a), IoSlice::new(b)]) — writev on Unix, WSASend on Windows) to gather multiple discontiguous byte slices in a single kernel call:

use std::fs::{self, File};
use std::io::{self, IoSlice, Write};

fn main() -> io::Result<()> {
    let path1 = std::env::temp_dir().join("rust_ch50_fswrite.txt");
    let path2 = std::env::temp_dir().join("rust_ch50_vectored.txt");

    // 1. One-shot `std::fs::write` (accepts `&str`, `String`, `&[u8]`, `Vec<u8>`):
    fs::write(&path1, "written via std::fs::write\n")?;

    // 2. Writing multiple slices to an open `File` via `write_all` and `IoSlice`:
    {
        let mut f = File::create(&path2)?;
        f.write_all(b"prefix: ")?;

        let slices = [IoSlice::new(b"part_a + "), IoSlice::new(b"part_b\n")];
        let _ = f.write_vectored(&slices)?;
    }

    print!("path1: {}", fs::read_to_string(&path1)?);
    print!("path2: {}", fs::read_to_string(&path2)?);

    fs::remove_file(&path1)?;
    fs::remove_file(&path2)?;
    Ok(())
}
path1: written via std::fs::write
path2: prefix: part_a + part_b