5

単純な TCP ベースのエコー サーバーを作成しています。を使用BufReaderBufWriterて a から読み書きしようとしたとき、 aを値で渡すとその所有権が移動し、 a に渡すことができないことがTcpStreamわかりました。次に、このスレッドで問題を解決する答えを見つけました。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)ました。BufReadそれで、Rust標準ライブラリのソースコードを開いて、これを見ました:

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

&mut BufReaderここで、自己 (私の場合は a かもしれません) を に渡していることがわかります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);
        }
    }
}

この部分では、BufReader:r.fill_buf()とを使用している場所が 2 つありr.consume(used)ます。私r.fill_buf()が見たいものだと思いました。BufReaderしたがって、 Rust標準ライブラリのコードに行き、これを見つけました:

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()ですか?

4

1 に答える 1

2

簡単な答え:ではなく&TcpStreamasを渡します。したがって、ではありません。ドキュメントでわかるように、実装されています。R: ReadTcpStreamselfRead::read&mut & TcpStream&mut TcpStreamRead&TcpStream

この作業コードを見てください:

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

不変のものへの可変参照を持つだけで、それを不変に使用するため、streamとしてもバインドされないことに注意してください。mut


次に、なぜReadを に実装できるのかを尋ねることができます。なぜなら、読み取り操作中に何か&TcpStreamを変更する必要があるからです。

これは、素晴らしい Rust の世界 ☮ が終わり、邪悪な C/オペレーティング システムの世界が始まる場所です。たとえば、Linux では、ストリームの「ファイル記述子」として単純な整数があります。これは、読み取りと書き込みを含む、ストリーム上のすべての操作に使用できます。整数を値で渡すため (これもCopy-type です)、整数への参照が変更可能か不変かは関係ありません。コピーするだけでよいからです。

したがって、オペレーティング システムまたは Rust 実装によって最小限の同期を実行する必要がありますstd。これは、通常、不変の参照を介して変更するのは奇妙で危険であるためです。この動作は「内部可変性」と呼ばれ、もう少し詳しく読むことができます...

于 2016-03-26T10:12:58.627 に答える