I just want to put a formatted string in a byte array. To use it I obviously need the length of the output, but I did not find how to do it properly. Everything I come up with is just stupid... like this:
use std::io::Write;
fn main() {
let mut buf = [0u8; 65536];
let s = "string";
let mut remlen = buf.len();
{
let mut x = &mut buf[..];
write!(x, "test {} 123", s).unwrap();
remlen = x.len();
}
println!(
"{}",
std::str::from_utf8(&buf[..(buf.len() - remlen)]).unwrap()
);
}
The last println is just to check. In reality I want to send the buffer over a socket.
I'd like to avoid the Heap allocation if thats possible. Sorry I am really new to Rust, but this seems like something that should not be that hard. I mean the write! macro is there... I am probably just not able to use it correctly
AFAIK formatting strings will always return a heap allocated string because it's unknown beforehand what the size of the arguments will be so it can't be stack allocated.
However if your socket implements std::io::Write, there should be no need to format the strings, just write each piece in the writer subsequently. For that you will need to transform each piece to &[u8]. For strings (heap allocated or not) that can be done with as_bytes.
Formatting won't allocate at all on it's own. So using write!(socket, "hello {}", "bar") won't allocate. For details, see the core::fmt::Arguments type.
Here's another option, which uses only the standard library:
use std::io::{Cursor, Write};
fn main() {
let mut buf = [0u8; 65536];
let mut cursor = Cursor::new(&mut buf[..]);
let s = "string";
write!(cursor, "test {} 123", s).unwrap();
let len = cursor.position() as usize;
println!("{}", std::str::from_utf8(&buf[..len]).unwrap());
}
I am really surprised how elegant the Write trait can be used to compose different things. Writing directly to the socket is awesome, but unfortunately I am using tokio. I guess it does not work that easily there...
Sockets from tokio normally implement AsyncWrite, which just takes a &[u8] as well. The user facing methods are in an extension trait AsyncWriteExt. You shouldn't have to make an array specifically and shouldn't have to worry about the exact size if all you want to do is send the bytes into the socket.
use tokio::net::TcpStream;
use tokio::prelude::*;
use std::error::Error;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Connect to a peer
let mut stream = TcpStream::connect("127.0.0.1:8080").await?;
let s = "string";
// Write some data.
stream.write(b"test ").await?;
stream.write(s.as_bytes()).await?;
stream.write(b" 123").await?;
Ok(())
}
If you really want a formatted string, it can also take a buffer in through AsyncWriteExt::write_buf, which will take a std::io::Cursor.