rust-lang / rust-lang/rust

Docs on safety of BufWriter are misleading

Open
#136,025 8 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-docs T-libs
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

Location

https://github.com/rust-lang/rust/blob/8231e8599e238ff4e717639bd68c6abb8579fe8d/library/std/src/io/buffered/bufwriter.rs#L9

Summary

This one is long winded. Sorry. A reader may decide this represents an actual code bug. I believe it is at least a documentation bug. I may just be rediscovering something every other programmer already knows.

let mut file = std::fs::File::create("test")?;
file.write_all(b"Hello, world!")?;
file.flush()?; // This does nothing. It is implemented as return Ok()
file.sync_all()?; // This issues an fsync
drop(file); // Close happens here. Docs correctly say that errors are ignored, sync_all *must* have happened to force the fsync.

In my scenario I am writing a file to an NFS mount from Linux. The syscalls are of the pattern open+write+write+...+close.
Close can fail, and that can prevent data from previous writes (which reported success) from being actually saved. I have no way of seeing the output of close. So I need to call sync_all, giving me a safe open+write+write+...+fsync+close.

https://github.com/rust-lang/rust/blob/8231e8599e238ff4e717639bd68c6abb8579fe8d/library/std/src/io/buffered/bufwriter.rs#L9

/// It can be excessively inefficient to work directly with something that
/// implements [Write]. [...]
///
/// BufWriter<W> can improve the speed of programs that make small and
/// repeated write calls to the same file or network socket. [...]
///
/// It is critical to call [flush] before BufWriter<W> is dropped. Though
/// dropping will attempt to flush the contents of the buffer, any errors
/// that happen in the process of dropping will be ignored. Calling [flush]
/// ensures that the buffer is empty and thus dropping will not even attempt
/// file operations.

This really advertises itself for the use case

let mut buf = BufWriter::new(std::fs::File::create("test")?)

and then gives guidance on how to use it safely. This talks about errors during drop being lost (correct), says that calling flush is critical (true, but in a misleading way as the call to the inner file.flush is pointless). It does talk about files, not any old Write, steering me further towards madness.

Because I need to call

buf.into_inner()?.sync_all()?;

to avoid risk of silent data loss that is otherwise unobservable to the application.

I propose changing the wording at https://github.com/rust-lang/rust/blob/8231e8599e238ff4e717639bd68c6abb8579fe8d/library/std/src/io/buffered/bufwriter.rs#L21 to indicate that flush is necessary but not sufficient, and perhaps also adding as an example use of into_inner().

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Read the documentation comments around lines 9-21 of library/std/src/io/buffered/bufwriter.rs, focusing on the guidance about flush during drop. Revise the wording to explain that flush is necessary but not sufficient for safely persisting file data, and consider the proposed into_inner().sync_all() example. Done means the documentation no longer implies that flush alone handles errors from closing or persisting the underlying file.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
documentation
Issue type
Documentation
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.