Format into a fixed buffer.
```rust use fmtbuf::WriteBuf; use std::fmt::Write;
fn main() { let mut buf: [u8; 10] = [0; 10]; let mut writer = WriteBuf::new(&mut buf); if let Err(e) = write!(&mut writer, "🚀🚀🚀") { println!("write error: {e:?}"); } let writtenlen = match writer.finish() { Ok(len) => len, // <- won't be hit since 🚀🚀🚀 is 12 bytes Err(len) => { println!("writing was truncated"); len } }; let written = &buf[..writtenlen]; println!("wrote {writtenlen} bytes: {written:?}"); println!("result: {:?}", std::str::fromutf8(written)); } ```
🚀🚀
The primary use case is for implementing APIs like strerror_r
, where the
user provides the buffer.
```rust use std::{ffi, fmt::Write, io::Error}; use fmtbuf::WriteBuf;
pub extern "C" fn mylibstrerror(err: *mut Error, buf: *mut ffi::cchar, buflen: usize) { let mut buf = unsafe { // Buffer provided by a users let mut buf = std::slice::fromrawpartsmut(buf as *mut u8, buf_len); }; let mut writer = WriteBuf::new(buf);
// Use the standard `write!` macro (no error handling for brevity)
write!(writer, "{}", err.as_ref().unwrap()).unwrap();
let _ =
if writer.truncated() {
// the message was truncated, let the caller know by adding "..."
writer.finish_with(b"...\0")
} else {
// just null-terminate the buffer
writer.finish_with(b"\0")
};
} ```
&mut [u8]
?The Rust Standard Library trait std::io::Write
is
implemented for &mut [u8]
which could be used instead of this library.
The problem with this approach is the lack of UTF-8 encoding support (also, it is not available in #![no_std]
).
```rust use std::io::{Cursor, Write};
fn main() { let mut buf: [u8; 10] = [0; 10]; let mut writer = Cursor::<&mut [u8]>::new(&mut buf); if let Err(e) = write!(&mut writer, "rocket: 🚀") { println!("write error: {e:?}"); } let writtenlen = writer.position() as usize; let written = &buf[..writtenlen]; println!("wrote {writtenlen} bytes: {written:?}"); println!("result: {:?}", std::str::fromutf8(written)); } ```
Running this program will show you the error:
text
write error: Error { kind: WriteZero, message: "failed to write whole buffer" }
wrote 10 bytes: [114, 111, 99, 107, 101, 116, 58, 32, 240, 159]
result: Err(Utf8Error { valid_up_to: 8, error_len: None })
The problem is that "rocket: 🚀"
is encoded as the 12 byte sequence -- the 🚀 emoji is encoded in UTF-8 as the 4 bytes
b"\xf0\x9f\x9a\x80"
-- but our target buffer is only 10 bytes long.
The write!
to the cursor naïvely cuts off the 🚀 mid-encode, making the encoded string invalid UTF-8, even though it
advanced the cursor the entire 10 bytes.
This is expected, since std::io::Write
comes from io
and does not know anything about string encoding; it operates
on the u8
level.
One could use the std::str::Utf8Error
to properly
cut off the buf
.
The only issue with this is performance.
Since std::str::from_utf8
scans the whole string moving forward, it costs O(n) to test this, whereas fmtbuf
will
do this in O(1), since it only looks at the final few bytes.