2016-03-26 4 views
2

Я пишу простой TCP-based эхо-сервер. Когда я попытался использовать BufReader и BufWriter для чтения и записи в TcpStream, я обнаружил, что передача TcpStream в BufReader::new() по значению перемещает его право собственности, так что я не мог передать его BufWriter. Тогда я нашел ответ в this thread, который решает эту проблему:Почему я могу просто передать неизменяемую ссылку на BufReader вместо изменчивой ссылки?

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() вместо изменчивой ссылки?

Всю программу можно найти here.

Подробнее

В приведенном выше коде, я использовал reader.read_line(&mut message). Поэтому я открыл исходный код BufRead в стандартной библиотеке ржавчины и увидел это:

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

Здесь мы видим, что он проходит сам (который может быть &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); 
     } 
    } 
} 

В этой части есть два места, используя BufReader: r.fill_buf() и r.consume(used). Я думал, что я хочу видеть r.fill_buf(). Поэтому я пошел к коду 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()?

+0

дубликата [Почему можно реализовать читать неизменяемая ссылка на файл?] (http://stackoverflow.com/q/31503488/155423). – Shepmaster

+0

@Shepmaster Я изначально думал, что TcpStream работает по-другому из файла. Но, прочитав ответ Лукаса Кальбердотта, я внезапно понял, что за этим стоит то же самое. Спасибо за вашу ссылку. –

ответ

2

Быстрый Anser: мы проходим &TcpStream в R: Read, не TcpStream. Таким образом, self в Read::read является &mut & TcpStream, а не &mut TcpStream. Read предназначен для &TcpStream, как вы можете видеть in the documentation.

Посмотрите на этом рабочем коде:

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

Обратите внимание, что stream даже не связан, как mut, потому что мы используем его непреложны, просто имея изменяемую ссылку на незыблемые один.


Следующая, вы могли бы спросить, почему Read могут быть реализованы для &TcpStream, потому что это необходимо мутировать что-то во время операции чтения.

Здесь заканчивается красивый мир ржавчины,, и начинается злая C-/операционная система-мир. Например, в Linux у вас есть простое целое число как «файловый дескриптор» для потока. Вы можете использовать это для всех операций над потоком, включая чтение и запись. Поскольку вы передаете целое число по значению (это также тип Copy), не имеет значения, есть ли у вас изменяемая или неизменяемая ссылка на целое число, которое вы можете просто скопировать.

Поэтому минимальная синхронизация должна выполняться операционной системой или реализацией Rust std, потому что обычно странно и опасно мутировать через неизменяемую ссылку. Такое поведение называется «внутренняя Изменчивость», и вы можете прочитать немного больше об этом ...

+0

Я полностью не ожидал, что 'self' будет' & mut & TcpStream' !! Но что это такое? Я имею в виду ... что такое изменяемая ссылка на ссылку? Если бы я назвал 'self.something', это бы имело тот же эффект, когда' self' был 'TcpStream' или' & TcpStream'? –

+0

@YushanLin * будет иметь такой же эффект * -> в этом случае да. Точечный синтаксис делает несколько вещей, таких как deref-coercions. Эта тема слишком велика для последующего комментария^_^ –

+0

Хорошо. Спасибо за ваш ответ. Ваше дополнение о том, почему 'Read' может быть реализовано для неизменной ссылки, полезно! Ваш ответ мне очень помогает. –

 Смежные вопросы

  • Нет связанных вопросов^_^