Skip to content
Select theme

How to write to a file

Short answer: std::fs::write(path, contents) writes the whole buffer to a file in one call: it creates the file if it isn’t there, and truncates it to zero bytes first if it is. It returns io::Result<()> — the call writes every byte or tells you it didn’t — so a missing parent directory or a permission problem comes back as an Err you can inspect.

From a fresh project — cargo new write-demo, then replace the contents of src/main.rs with each block below and run it with cargo run. The project name shows up only in cargo’s own output; nothing else on this page depends on it. Each block writes the file it then reads, so it runs on its own and in any order; in your own code the file is already there and you delete the write line. Paths are relative to the directory you run from, which under cargo run is the project root.

Leaves are falling
softly on the path
38 bytes

One call opens the file, writes every byte and closes it — no buffer to size, no loop. The contents argument is anything that can be viewed as a slice of bytes: &str and &[u8] both go in:

8 bytes

There is no encoding step and no check: a &str goes out as the bytes it is made of, a &[u8] as it stands. Reading it back as text is a decision made on the way in — a String can only hold valid UTF-8.

The file already exists? It gets truncated

Section titled “The file already exists? It gets truncated”

fs::write opens with create-and-truncate, so an existing file is emptied before the new bytes land. Nothing of the old contents survives — not even past the end of the new ones:

34 bytes after the first write
short
6 bytes after the second

This is the behaviour that catches people out: rewriting a config file with a shorter body leaves no tail of the old one. Adding to a file instead of replacing it is a different call — OpenOptions with append set — and that has its own page later in this chapter.

The failure everyone hits first is a parent directory that isn’t there:

No such file or directory (os error 2)
Os { code: 2, kind: NotFound, message: "No such file or directory" }
NotFound: true
Os { code: 21, kind: IsADirectory, message: "Is a directory" }
Os { code: 13, kind: PermissionDenied, message: "Permission denied" }

Each failure has a kind: NotFound when a parent directory isn’t there, IsADirectory when the path is one, PermissionDenied when the file is read-only. fs::write creates the file, not the directories on the way to it; create_dir_all does that, later in this chapter. Match on err.kind() when a failure is acceptable, and propagate the rest with ?.

When the bytes arrive in pieces, or the buffer is too big to hold at once, open the file and write to it yourself:

18 bytes

File::create is the same create-or-truncate open fs::write does, minus the writing: an existing file is emptied first, and everything you write lands from the start. write_all, which is what writeln! calls underneath, loops until every byte is in, so a short write surfaces as an error instead of silent data loss. File is unbuffered — each call is a system call — so many small pieces want a BufWriter around the file. Nothing needs flushing when you are done: the bytes already went to the OS, and the file closes when file goes out of scope.