6

I am writing a simple TCP-based echo server. When I tried to use BufReader and BufWriter to read from and write to a TcpStream, I found that passing a TcpStream to BufReader::new() by value moves its ownership so that I couldn't pass it to a BufWriter. Then, I found an answer in this thread that solves the problem:

fn handle_client(stream: TcpStream) {
    let mut reader = BufReader::new(&stream);
    let mut writer = BufWriter::new(&stream);

    // Receive a message
    let mut message = String::new();
    reader.read_line(&mut message).unwrap();

    // ingored
}

This is simple and it works. However, I can not quite understand why this code works. Why can I just pass an immutable reference to BufReader::new(), instead of a mutable reference ?

The whole program can be found here.

More Details

In the above code, I used reader.read_line(&mut message). So I opened the source code of BufRead in Rust standard library and saw this:

fn read_line(&mut self, buf: &mut String) -> Result<usize> {
    // ignored
    append_to_string(buf, |b| read_until(self, b'\n', b))
}

Here we can see that it passes the self (which may be a &mut BufReader in my case) to read_until(). Next, I found the following code in the same file:

fn read_until<R: BufRead + ?Sized>(r: &mut R, delim: u8, buf: &mut Vec<u8>)
                                   -> Result<usize> {
    let mut read = 0;
    loop {
        let (done, used) = {
            let available = match r.fill_buf() {
                Ok(n) => n,
                Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
                Err(e) => return Err(e)
            };
            match memchr::memchr(delim, available) {
                Some(i) => {
                    buf.extend_from_slice(&available[..i + 1]);
                    (true, i + 1)
                }
                None => {
                    buf.extend_from_slice(available);
                    (false, available.len())
                }
            }
        };
        r.consume(used);
        read += used;
        if done || used == 0 {
            return Ok(read);
        }
    }
}

In this part, there are two places using the BufReader: r.fill_buf() and r.consume(used). I thought r.fill_buf() is what I want to see. Therefore, I went to the code of BufReader in Rust standard library and found this:

fn fill_buf(&mut self) -> io::Result<&[u8]> {
    // ignored
    if self.pos == self.cap {
        self.cap = try!(self.inner.read(&mut self.buf));
        self.pos = 0;
    }
    Ok(&self.buf[self.pos..self.cap])
}

It seems like it uses self.inner.read(&mut self.buf) to read the data from self.inner. Then, we take a look at the structure of BufReader and the BufReader::new():

pub struct BufReader<R> {
    inner: R,
    buf: Vec<u8>,
    pos: usize,
    cap: usize,
}

// ignored
impl<R: Read> BufReader<R> {
    // ignored
    #[stable(feature = "rust1", since = "1.0.0")]
    pub fn new(inner: R) -> BufReader<R> {
        BufReader::with_capacity(DEFAULT_BUF_SIZE, inner)
    }

    // ignored
    #[stable(feature = "rust1", since = "1.0.0")]
    pub fn with_capacity(cap: usize, inner: R) -> BufReader<R> {
        BufReader {
            inner: inner,
            buf: vec![0; cap],
            pos: 0,
            cap: 0,
        }
    }

    // ignored
}

From the above code, we can know that inner is a type which implements Read. In my case, the inner may be a &TcpStream.

I knew the signature of Read.read() is:

fn read(&mut self, buf: &mut [u8]) -> Result<usize>

It requires a mutable reference here, but I only lent it an immutable reference. Is this supposed to be a problem when the program reaches self.inner.read() in fill_buf() ?

Community
  • 1
  • 1
Yushan Lin
  • 65
  • 7
  • Duplicate of [Why is it possible to implement Read on an immutable reference to File?](http://stackoverflow.com/q/31503488/155423). – Shepmaster Mar 26 '16 at 12:52
  • @Shepmaster I originally thought TcpStream works in the different way from File. But after reading Lukas Kalbertodt's answer, I suddenly realized the idea behind it is the same. Thank you for your link. – Yushan Lin Mar 27 '16 at 08:11

1 Answers1

2

Quick anser: we pass a &TcpStream as R: Read, not TcpStream. Thus self in Read::read is &mut & TcpStream, not &mut TcpStream. Read is implement for &TcpStream as you can see in the documentation.

Look at this working code:

let stream = TcpStream::connect("...").unwrap();
let mut buf = [0; 100];
Read::read(&mut (&stream), &mut buf);

Note that stream is not even bound as mut, because we use it immutably, just having a mutable reference to the immutable one.


Next, you could ask why Read can be implemented for &TcpStream, because it's necessary to mutate something during the read operation.

This is where the nice Rust-world ☮ ends, and the evil C-/operating system-world starts . For example, on Linux you have a simple integer as "file descriptor" for the stream. You can use this for all operations on the stream, including reading and writing. Since you pass the integer by value (it's also a Copy-type), it doesn't matter if you have a mutable or immutable reference to the integer as you can just copy it.

Therefore a minimal amount of synchronization has to be done by the operating system or by the Rust std implementation, because usually it's strange and dangerous to mutate through an immutable reference. This behavior is called "interior mutability" and you can read a little bit more about it...

Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
  • I totally didn't expect `self` is a `&mut & TcpStream`!! But, what exactly is it ? I mean... what is a mutable reference to a reference ? If I called `self.something`, would that have the same effect when `self` was a `TcpStream` or a `&TcpStream` ? – Yushan Lin Mar 27 '16 at 08:28
  • @YushanLin *would that have the same effect* -> in this case yes. The dot-syntax does a few things, like deref-coercions. This topic is too big for a follow up comment ^_^ – Lukas Kalbertodt Mar 27 '16 at 23:54
  • Ok. Thanks for your reply. Your supplement about why `Read` can be implemeneted for an immutable reference is useful !! Your answer helps me a lot. – Yushan Lin Mar 28 '16 at 05:52