However, if I were to change the line let mut message: Vec<u8> = vec![0; 256]; to let mut message: Vec<u8> = vec![];, I will not be able to send any data on the TCP connection (nothing shows up on the remote server's console output which is running nc).
Why is that the case?
What if I do not know the full size of the message beforehand?
AsyncReadExt::read does not grow the message buffer. So if the buffer has a length of 0, which is the case when you create a vector with vec![], you won't be able to read any bytes from tokio_stdin, henceforth not forwarding any data to your write_half.
According to the tokio docs you probably shouldn't use tokios stding:
This handle is best used for non-interactive uses, such as when a file is piped into the application. For technical reasons, stdin is implemented by using an ordinary blocking read on a separate thread, and it is impossible to cancel that read. This can make shutdown of the runtime hang until the user presses enter.
For interactive uses, it is recommended to spawn a thread dedicated to user input and use blocking IO directly in that thread.
Instead you should probably use stds stdin
use std::io;
fn main() -> io::Result<()> {
let mut buffer = String::new();
io::stdin().read_line(&mut buffer)?;
Ok(())
}
AsyncReadExt::read_to_end appends the read data to the buffer, growing it when necessary. AsyncBufRead::read_line would be an appropriate method for reading interactively from stdin. There are also the copy and copy_buf functions for conveniently copying bytes from a reader to a writer.
To expand just a little on @jofas's answer, the async I/O APIs are designed to mirror the sync stdlib I/O APIs, for consistency. So you can expect the sync APIs to behave in the same way in terms of which methods can grow the buffer and which cannot.
Not directly related to the question, as it has been already answered, but there are a couple more things you could improve in your snippet.
You should declare your buffer outside IO loop. Now each time you loop you allocate and deallocate heap memory. Maybe compiler can optimize this out, but I don't know it and I don't think this should be relied upon.
You have a logic bug. AsyncReadExt::read (similar to std's Read) returns Result<usize>, where Ok(n) means that n bytes were written to the buffer. But you are writing a whole buffer to the write_half each time, which will result in incorrect data being send. Using tokio::io::copy[_buf] as @jofas has suggested will also fix this.