2016-03-26 16 views
2

Ich schreibe einen einfachen TCP-basierten Echo-Server. Als ich versuchte, BufReader und BufWriter zu verwenden, um von einem TcpStream zu lesen und zu schreiben, fand ich, dass das Übergeben eines TcpStream zu BufReader::new() wertmäßig seinen Besitz verschiebt, damit ich es zu einem BufWriter nicht übergeben konnte. Dann fand ich eine Antwort in this thread, die das Problem löst:Warum kann ich einen unveränderlichen Verweis auf BufReader statt einer veränderbaren Referenz übergeben?

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 
} 

Das ist einfach und es funktioniert. Ich kann jedoch nicht ganz verstehen, warum dieser Code funktioniert. Warum kann ich einfach eine unveränderliche Referenz an BufReader::new() anstelle einer veränderbaren Referenz übergeben?

Das gesamte Programm kann here gefunden werden.

Details

In dem obigen Code, habe ich reader.read_line(&mut message). Also öffnete ich den Quellcode von BufRead in Rust Standard-Bibliothek und sah dies:

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

Hier können wir sehen, dass es die Selbst geht (die ein &mut BufReader in meinem Fall sein kann) zu 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); 
     } 
    } 
} 

In diesem Teil gibt es zwei Orte mit den BufReader: Als nächst ich den folgenden Code in der gleichen Datei gefunden r.fill_buf() und r.consume(used). Ich dachte r.fill_buf() ist was ich sehen will. Deshalb ging ich in den Code von BufReader in Rust Standardbibliothek und diese gefunden:

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

Es scheint, wie es self.inner.read(&mut self.buf) verwendet die Daten von self.inner zu lesen. Dann haben wir einen Blick auf die Struktur von BufReader nehmen und die 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 
} 

Aus dem obigen Code können wir wissen, dass inner ein Typ ist, der Read implementiert. In meinem Fall kann die inner eine &TcpStream sein.

wusste, dass ich die Unterschrift von Read.read() ist:

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

Es eine Referenz wandelbar erfordert hier, aber ich es nur einen unveränderlich Bezug verliehen. Soll das ein Problem sein, wenn das Programm self.inner.read() in fill_buf() erreicht?

+0

Duplizieren von [Warum ist es möglich umzusetzen lesen Lesen Sie auf ein unveränderlicher Verweis auf Datei?] (http://stackoverflow.com/q/31503488/155423). – Shepmaster

+0

@Shempmaster Ich dachte ursprünglich, dass TcpStream anders funktioniert als File. Aber nachdem ich Lukas Kalbertodts Antwort gelesen hatte, wurde mir plötzlich klar, dass die Idee dahinter dieselbe ist. Danke für deinen Link. –

Antwort

2

Schnell Anser: Wir passieren eine &TcpStream als R: Read, nicht TcpStream. Somit ist self in Read::read&mut & TcpStream, nicht &mut TcpStream. Read ist für &TcpStream implementieren, wie Sie in the documentation sehen können.

Schauen Sie sich das Arbeitscode:

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

Beachten Sie, dass stream nicht einmal als mut gebunden ist, weil wir es unabänderlich verwenden, nur um den unveränderlich man eine veränderbare Referenz aufweist.


Weiter, könnte man fragen, warum Read für &TcpStream umgesetzt werden können, weil es notwendig ist etwas während des Lesevorgangs zu mutieren.

Hier endet die schöne Rust-Welt and und die böse C-/Betriebssystem-Welt beginnt. Unter Linux haben Sie beispielsweise eine einfache Ganzzahl als "Dateideskriptor" für den Stream. Sie können dies für alle Vorgänge im Stream verwenden, einschließlich Lesen und Schreiben. Da Sie die Ganzzahl nach Wert übergeben (es ist auch ein Copy -Typ), ist es egal, ob Sie einen veränderbaren oder unveränderlichen Verweis auf die Ganzzahl haben, wie Sie es einfach kopieren können.

Daher muss eine minimale Synchronisation vom Betriebssystem oder von der Rust std Implementierung durchgeführt werden, da es normalerweise seltsam und gefährlich ist, durch eine unveränderliche Referenz zu mutieren. Dieses Verhalten wird als „innere Wandelbarkeit“ genannt und man kann ein bisschen mehr darüber ...

+0

Ich habe nicht erwartet, dass "self" ein '& mut & TcpStream' ist !! Aber was genau ist es? Ich meine ... was ist ein veränderlicher Verweis auf eine Referenz? Wenn ich "self.something" nennen würde, hätte das den gleichen Effekt, wenn "self" ein "TcpStream" oder ein "& TcpStream" wäre? –

+0

@YushanLin * hätte das den gleichen Effekt * -> in diesem Fall ja. Die Punktsyntax macht ein paar Dinge, wie zum Beispiel Verzugszerrungen. Dieses Thema ist zu groß für einen Folgekommentar^_^ –

+0

Ok. Danke für deine Antwort. Ihre Ergänzung darüber, warum 'Read' für eine unveränderliche Referenz implementiert werden kann, ist nützlich !! Deine Antwort hilft mir sehr. –