2

All methods in the io::Write trait accept &mut self. It makes sense since conceptually it needs to mutate some internal state. For the same reason, it is only natural to see it has a blanket implementation for &mut W if W is io::Write:

impl<'_, W: Write + ?Sized> Write for &'_ mut W

On the other hand, I find it strange that it is also implemented for &File and &TcpStream in addition to File and TcpStream themselves.

This discrepancy bothers me a little bit. Is it done only for convenience? Or is it the nature of I/O make this safe?


Noticed this when trying out crate may, a stackful coroutines library. Its net::TcpStream doesn't have io::Write implemented for &TcpStream. So some code suddenly failed to compile:

use may::{coroutine::scope, go};  // may = "0.3"
use std::{
    error::Error,
    io::Write,
    net::TcpStream,
};

fn send_to_peer(msg: &str, peer: &TcpStream) -> Result<(), Box<dyn Error>>
{
    scope(|scope| {
        go!(scope, move || {
            let mut socket = peer;
            let _ = socket.write_all(msg.as_bytes());
        });
    });

    Ok(())
}

This compiles. Switch to may::net::TcpStream and it will fail.

edwardw
  • 12,652
  • 3
  • 40
  • 51
  • 3
    It is safe to write to a file while holding a shared reference. The in-process state of the file won't change, and the kernel takes care of concurrent access to the state of the file descriptor in the kernel. If the `Write` implementation did not exist, you'd need a mutex to write to a log file from multiple threads, making the performance worse than it needed to be. – Sven Marnach Feb 09 '20 at 22:03
  • 1
    I don't like it either, but that "safe". interior mutability is evil IMO. – Stargateur Feb 10 '20 at 05:06

1 Answers1

1

Wasn't intended to answer my own question, but @SvenMarnach's comment prompted me to check out the POSIX standard with respect to API thread safety. It addresses my core concern.

Specifically, the section 2.9.1 of its 2018 edition reads:

All functions defined by this volume of POSIX.1-2017 shall be thread-safe, except that the following functions need not be thread-safe.

And write is not among them. Furthermore, section 2.9.7 dictates:

All of the following functions shall be atomic with respect to each other in the effects specified in POSIX.1-2017 when they operate on regular files or symbolic links

And write should be atomic to other listed APIs.

Because of this thread-safety at operating system level, the Rust language level constructions, namely a value, its (shared) reference and its mutable (exclusive) reference get subsumed; they are all semantically thread-safe here.

The implementation of io::Write for &File and &TcpStream then serves as an optimization. Otherwise, one would need a Mutex to write to them from multiple threads, which introduces unnecessary overhead.

edwardw
  • 12,652
  • 3
  • 40
  • 51