Use String as buffer for reading from TcpStream

Is there a way to use a String::new() as a buffer for reading from a TcpStream ? I am using one as a buffer for writing, but i would like to be able to do the same for reading.

use std::net::TcpListener;
use std::io::{stdin, Read, Write};
use std::str;

pub fn socket_listen(addr: &String, port: &String) {

    // bind to socket
    let listner = TcpListener::bind(format!("{addr}:{port}")).unwrap();
    
    for stream in listner.incoming() {
        let mut stream = stream.unwrap();
        loop {
            // write
            let mut write_buf = String::new();
            stdin().read_line(&mut write_buf).unwrap();
            stream.write(write_buf.as_bytes()).unwrap();

            // read
            let mut read_buf= [0u8; 1024]; // <- I want read_buf to be a String::new() like write_buf
            let bytes_read = stream.read(&mut read_buf).unwrap();
            let data = &read_buf[..bytes_read];
            println!("{}", str::from_utf8(&data).unwrap());
        }
    }
}

When i have tried to do conversion acrobatics, i have gotten a lot of weird errors and i figured there must be a better way

It's possible with unsafe operations if you really want to:

  1. Call String::as_mut_vec() to get a &mut Vec<u8>.
  2. Call Vec::spare_capacity_mut() to get access to the unused memory in the Vec.
  3. Zero-initialize the spare capacity slice.
  4. Pass it to read().
  5. Check if the read data is valid UTF-8.
  6. Call Vec::set_len() to grow the string to contain the validated data.

But like any unsafe code, you shouldn't do this unless you're sure that this is a performance bottleneck in your application.

You can do it unsafely:

                    // read
                    let mut read_buf = String::new();
                    let bytes_read =
                        stream.read(unsafe { read_buf.as_mut_vec() }).unwrap();
                    let data = &read_buf[..bytes_read];
                    println!("{}", &data);

It is unsafe because the bytes being read may not be valid utf-8. There may be a better way.

Do you want to allocate a buffer for every read as in your example, or will you be reusing a buffer?


This works without unsafe, whether reusing the read buffer or not:

            for stream in listner.incoming() {
                let mut stream = stream.unwrap();
                let mut read_buf = Vec::new();
                loop {
                    // write
                    let mut write_buf = String::new();
                    stdin().read_line(&mut write_buf).unwrap();
                    stream.write(write_buf.as_bytes()).unwrap();

                    // read
                    read_buf.truncate(0);
                    stream.read(&mut read_buf).unwrap();
                    let string = String::from_utf8(read_buf).unwrap();
                    println!("{}", string);
                    read_buf = string.into_bytes();
                }
            }

The conversions between String and Vec<u8> in both directions are simple moves, the data is not copied and there is no allocation.

1 Like