std::io::Write: "[a] call to write represents at most one attempt to write to any wrapped object"


#1

The documentation for Write::write says that

A call to write represents at most one attempt to write to any wrapped object.

A strict interpretation of this poses problems for writer wrappers like compressors that have to buffer data, process it, and then write it out. If the internal buffer is full, the only thing write can do is write the internal buffer out and then return an error indicating that the caller must retry (the only reasonable option here is ErrorKind::Interrupted).

However, in this GitHub comment @alexcrichton says that

Note that the comment in the docs there was largely targeted at atomicity. Basically if you successfully write any bytes then that must be reported back. I think it’s fine, semantically, for an auto-retry to happen on EINTR.

This viewpoint makes sense, but it doesn’t really match my interpretation of the documentation. I also don’t know what the pitfalls of this would be.

Is it OK for a wrapped writer to call the inner write multiple times at all? If it is, the documentation should probably be updated to reflect that.


#2

It depends on how high or low level you view your writer. If you’re at the low level of the stdlib, you should expose the underlying errors. If you’re giving some convenience function, it’s almost certainly fine to eat annoying errors like EINTR which just allow for bugs.

I mean, do you expect people to call your code using

mystruct.write(&buf)?;

If so, then this won’t get the chance to retry if you expose EINTR. That’s almost certainly wrong.

EDIT: in fact, I think the docs could definitely be updated to point out that using std::fs::File::write and friends using try! and ? is a bad idea for exactly the aforementioned reason.


#3

Hmm, but should typical application code be calling write directly at all? In most cases it’s just things like wrappers and combinators that would be calling the lower-level write.

In any case the docs seem to be pretty unclear about this.


#4

FWIW, I submitted a documentation issue here.

but should typical application code be calling write directly at all?

I should hope so! I thought Write was like the Writer interface in Go: a useful lingua franca concept for something that moves data in one direction.

Otherwise, you can set SA_RESTART as a sigaction and let the underlying libc handle restarts to syscalls.


#5

But in this case, Write exposes write_all, which probably does what people want. write itself looks like a lower-level block. Apart from wrappers (and some asynchronous io specifics), I’m not sure when it’d make sense to use write instead of write_all.


#6

This documentation dates all the way back to the inception of the I/O module, and I think it’s just a bug in the docs. The intention here was to convey concepts such as:

  • File::write invokes the write syscall at most once.
  • BufWriter::write will only try to write its internal buffer once
  • Implementations of Write shouldn’t spin indefinitely if they get back “would block” by accident

etc. Basically all implementations of Write::write are intended to be “as low level as they can be” for that particular implementation, sorta mostly defined by that implementation. If your implementation is “retry on EINTR” then I think that’s a perfectly valid Write implementation