> On 11 Jul 2025, at 08:43, Andreas Hindborg <a.hindborg@xxxxxxxxxx> wrote: > > Add `NullTerminatedFormatter`, a formatter that writes a null terminated > string to an array or slice buffer. Because this type needs to manage the > trailing null marker, the existing formatters cannot be used to implement > this type. > > Signed-off-by: Andreas Hindborg <a.hindborg@xxxxxxxxxx> > --- > rust/kernel/str.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ > 1 file changed, 50 insertions(+) > > diff --git a/rust/kernel/str.rs b/rust/kernel/str.rs > index b1bc584803b0..c58925438c6e 100644 > --- a/rust/kernel/str.rs > +++ b/rust/kernel/str.rs > @@ -838,6 +838,56 @@ fn write_str(&mut self, s: &str) -> fmt::Result { > } > } > > +/// A mutable reference to a byte buffer where a string can be written into. > +/// > +/// The buffer will be automatically null terminated after the last written character. Hmm, I suppose you want this to be the only null? See below. > +/// > +/// # Invariants > +/// > +/// `buffer` is always null terminated. > +pub(crate) struct NullTerminatedFormatter<'a> { > + buffer: &'a mut [u8], > +} > + > +impl<'a> NullTerminatedFormatter<'a> { > + /// Create a new [`Self`] instance. > + pub(crate) fn new(buffer: &'a mut [u8]) -> Option<NullTerminatedFormatter<'a>> { > + *(buffer.first_mut()?) = 0; > + > + // INVARIANT: We null terminated the buffer above. > + Some(Self { buffer }) > + } > + > + #[expect(dead_code)] > + pub(crate) fn from_array<const N: usize>( > + buffer: &'a mut [crate::ffi::c_char; N], > + ) -> Option<NullTerminatedFormatter<'a>> { > + Self::new(buffer) > + } > +} > + > +impl Write for NullTerminatedFormatter<'_> { > + fn write_str(&mut self, s: &str) -> fmt::Result { > + let bytes = s.as_bytes(); > + let len = bytes.len(); > + > + // We want space for a null terminator. Buffer length is always at least 1, so no overflow. Perhaps this should be a type invariant? I know this is a logical conclusion from saying “buffer is always NULL terminated”, but it’s always nice to be even more explicit. > + if len > self.buffer.len() - 1 { > + return Err(fmt::Error); > + } > + > + let buffer = core::mem::take(&mut self.buffer); > + // We break the null termination invariant for a short while. > + buffer[..len].copy_from_slice(bytes); > + self.buffer = &mut buffer[len..]; As I said in my first comment, if you want this to be the only null, I don’t think the copy above enforces it? > + > + // INVARIANT: We null terminate the buffer. > + self.buffer[0] = 0; > + > + Ok(()) > + } > +} > + > /// An owned string that is guaranteed to have exactly one `NUL` byte, which is at the end. > /// > /// Used for interoperability with kernel APIs that take C strings. > > -- > 2.47.2 > > > — Daniel