Как сохранить TcpStream с BufReader и BufWriter в структуре данных - PullRequest
1 голос
/ 19 октября 2019

Я хотел, чтобы TcpStream совместно использовался как BufReader, так и BufWriter, я нашел решение в:
Если BufReader становится владельцем потока, как я могу читать и писать строки наэто?

Теперь я хочу, чтобы это было в собственной структуре данных, но я получил только частичный ответ от:
Почему я не могу сохранить значение и ссылку на это значениев той же структуре?

Требуемая реализация нарушает правила владения.

use std::io::{BufReader, BufWriter};
use std::net::TcpStream;

pub struct BufTcpStream<'a> {
    _socket: TcpStream,
    input:  BufReader<&'a TcpStream>;
    output: BufWriter<&'a TcpStream>;
}

impl<'a> BufTcpStream<'a> {
    pub fn new(socket: TcpStream) -> Self {
        Self{
            input : BufReader::new(&socket),
            output: BufWriter::new(&socket),
            _socket: socket,//                 <-- MOVE OF BORROWED VALUE HERE
        }
    }
}

Чтобы решить эту проблему, я должен был убедиться, что ссылки TcpStream будут оставаться действительными в течение всехвремя жизни структуры, я использовал Pin<Box<TcpStream>>, чтобы гарантировать это.

Но компилятор все еще жалуется на перемещение заимствованного значения socket. Чтобы удалить этот барьер, я использовал std::meme::transmute().

Теперь, что я хочу знать:

Безопасна ли эта реализация?

use std::io::{BufReader, BufWriter};
use std::net::TcpStream;
use std::pin::Pin;

pub struct BufTcpStream<'a> {
    _socket: Pin<Box<TcpStream>>,
    input : BufReader<&'a TcpStream>,
    output: BufWriter<&'a TcpStream>,
}

impl<'a> BufTcpStream<'a> {
    pub fn new(socket: TcpStream) -> Self {
        let pin = Box::pin(socket);
        unsafe {
            Self{
                input : BufReader::new(std::mem::transmute(&*pin)),
                output: BufWriter::new(std::mem::transmute(&*pin)),
                _socket: pin,
            }
        }
    }
    pub fn reach(&mut self) -> (
        &mut BufReader<&'a TcpStream>,
        &mut BufWriter<&'a TcpStream>
    ) {
        (&mut self.input, &mut self.output)
    }
}

Ответы [ 2 ]

1 голос
/ 21 октября 2019

Используйте TcpStream::try_clone для получения второго потока:

Возвращенный TcpStream является ссылкой на тот же поток, на который ссылается этот объект. Обе ручки будут считывать и записывать один и тот же поток данных , а параметры, заданные для одного потока, будут распространяться на другой поток.

Затем можно обернуть один из них в считыватель иодин в писателе:

use std::{
    io::{self, BufReader, BufWriter},
    net::TcpStream,
};

struct BufTcpStream {
    input: BufReader<TcpStream>,
    output: BufWriter<TcpStream>,
}

impl BufTcpStream {
    fn new(stream: TcpStream) -> io::Result<Self> {
        let input = BufReader::new(stream.try_clone()?);
        let output = BufWriter::new(stream);

        Ok(Self { input, output })
    }
}

См. также:

0 голосов
/ 21 октября 2019

Нет. Эта реализация не является безопасной. По двум причинам.

1) Ссылка на TcpStream может жить дольше, чем сама структура

let bad_ref = {
    let buffer = BufTcpStream::new(...);
    let (i, o) = buffer.reach();
    *i.get_ref()
};// <-- buffer is dropped here
// bad_ref is now an invalid ref but the compiler won't see it
bad_ref.write(b"hello world").unwrap();

*i.get_ref() имеет тип &'a TcpStream, но &'a время жизни никогда не определяется компилятором, поэтому оно не учитывает это.

Это можно исправить в методе reach(), связав время жизни self с возвращенными ссылками. :

fn reach<'b>(&'b mut self) -> (
    &mut BufReader<&'b TcpStream>,
    &mut BufWriter<&'b TcpStream>
) {
    (&mut self.input, &mut self.output)
}


2) TcpStream отбрасывается до BufReader и BufWriter

pub struct BufTcpStream<'a> {
    _socket: Pin<Box<TcpStream>>,//       First to be dropped   |
    input : BufReader<&'a TcpStream>,//   Second to be dropped  |
    output: BufWriter<&'a TcpStream>,//   Last to be dropped    V
}

Это можно исправить, поместив _socket в качестве последнего элемента:

pub struct BufTcpStream<'a> {
    input : BufReader<&'a TcpStream>,//   First to be dropped   |
    output: BufWriter<&'a TcpStream>,//   Second to be dropped  |
    _socket: Pin<Box<TcpStream>>,//       Last to be dropped    V
}




На самом деле время жизни 'a в struct BufTcpStream<'a> бесполезно. Время жизни небезопасных ссылок может быть установлено на 'static.

Вот фиксированная реализация:

use std::io::{BufReader, BufWriter};
use std::net::TcpStream;
use std::pin::Pin;

pub struct BufTcpStream {
    input : BufReader<&'static TcpStream>,
    output: BufWriter<&'static TcpStream>,
    _socket: Pin<Box<TcpStream>>,
}

impl BufTcpStream {
    pub fn new(socket: TcpStream) -> Self {
        let pin = Box::pin(socket);
        unsafe {
            Self{
                input : BufReader::new(std::mem::transmute(&*pin)),
                output: BufWriter::new(std::mem::transmute(&*pin)),
                _socket: pin,
            }
        }
    }
    pub fn reach<'b>(&'b mut self) -> (
        &mut BufReader<&'b TcpStream>,
        &mut BufWriter<&'b TcpStream>
    ) {
        unsafe {
            std::mem::transmute((&mut self.input, &mut self.output))
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...