Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can I just pass an immutable reference to BufReader, instead of a mutable reference? [duplicate]

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() ?

like image 859
Yushan Lin Avatar asked Oct 31 '22 06:10

Yushan Lin


1 Answers

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...

  • in the cell documentation
  • in the book 📖
like image 110
Lukas Kalbertodt Avatar answered Nov 10 '22 18:11

Lukas Kalbertodt