为什么我只能传递一个不可变引用给BufReader,而不能传递一个可变引用?

6

我正在编写一个简单的基于TCP的回显服务器。当我尝试使用BufReaderBufWriterTcpStream读取和写入时,我发现通过值传递TcpStreamBufReader::new()会移动其所有权,因此我无法将其传递给BufWriter。然后,我在这个线程中找到了一个解决问题的答案:

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
}

这很简单并且有效。然而,我不太理解为什么这段代码能够工作。为什么我可以只传递一个不可变引用给BufReader::new(),而不是一个可变引用?
整个程序可以在这里找到。
更多细节:
在上述代码中,我使用了reader.read_line(&mut message)。所以我打开了Rust标准库中BufRead的源代码,并看到了这个:
fn read_line(&mut self, buf: &mut String) -> Result<usize> {
    // ignored
    append_to_string(buf, |b| read_until(self, b'\n', b))
}

在这里,我们可以看到它将self(在我的情况下可能是&mut BufReader)传递给了read_until()。接下来,在同一文件中我找到了以下代码:

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);
        }
    }
}

在这部分中,有两个地方使用了BufReaderr.fill_buf()r.consume(used)。我认为r.fill_buf()是我想要看到的。因此,我去查看了Rust标准库中BufReader的代码,并找到了以下内容:
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])
}

看起来它使用self.inner.read(&mut self.buf)self.inner读取数据。然后,我们来看一下BufReader的结构和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
}

通过以上代码,我们可以知道inner是一个实现了Read接口的类型。在我的情况下,inner可能是一个 &TcpStream

我了解Read.read()的签名为:

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

在这里需要一个可变引用,但我只借了一个不可变引用。当程序到达self.inner.read()fill_buf()中时,这会成为一个问题吗?


@Shepmaster 我最初认为TcpStream的工作方式与File不同。但是在阅读了Lukas Kalbertodt的答案之后,我突然意识到它背后的思想是相同的。感谢您提供的链接。 - Yushan Lin
1个回答

2

快速回答:我们将&TcpStream作为R: Read传递,而不是TcpStream。因此,在Read::read中的self&mut & TcpStream,而不是&mut TcpStreamRead已经为&TcpStream实现了,你可以在文档中看到。

请看下面这段可运行的代码:

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

请注意,stream甚至没有绑定为mut,因为我们只是对其进行不可变的使用,只是对不可变的引用进行可变引用。
接下来,您可能会问为什么可以为&TcpStream实现Read,因为在读取操作期间需要改变某些东西
这就是美好的 Rust 世界 ☮ 结束的地方,邪恶的 C-/操作系统世界开始了。例如,在 Linux 上,您有一个简单的整数作为流的“文件描述符”。您可以将其用于流上的所有操作,包括读取和写入。由于您通过值传递整数(它也是一种Copy-类型),因此无论您具有可变引用还是不可变引用,都没有关系,因为您只需复制它。
因此,操作系统或 Rust std 实现必须进行最少量的同步,因为通常通过不可变引用进行突变是奇怪和危险的。这种行为称为“内部可变性”,您可以在以下位置阅读更多信息...

我完全没想到 self 是一个 &mut & TcpStream!! 但是,它到底是什么?我的意思是... 可变引用指向引用是什么意思?如果我调用了 self.something,当 selfTcpStream 或者 &TcpStream 的时候,效果会一样吗? - Yushan Lin
@YushanLin 那会有相同的效果吗 -> 在这种情况下是的。点语法做了一些事情,比如解引用强制转换。这个话题太大了,需要回复评论 ^_^ - Lukas Kalbertodt
好的。感谢您的回复。关于为什么可以为不可变引用实现Read的补充说明非常有用!!您的答案对我帮助很大。 - Yushan Lin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接