ove/fmt.rs
1// Copyright (C) 2026 Kamil Lulko <kamil.lulko@gmail.com>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4//
5// This file is part of oveRTOS.
6
7//! Stack-allocated formatting buffer for `no_std` environments.
8//!
9//! [`FmtBuf`] implements [`core::fmt::Write`] so you can use `write!` macros
10//! without heap allocation, then pass the result to C APIs as a null-terminated
11//! byte slice via [`FmtBuf::as_cstr`].
12
13/// Zero-allocation stack buffer that implements [`core::fmt::Write`].
14///
15/// One byte is always reserved for a null terminator so that [`as_cstr`](FmtBuf::as_cstr)
16/// can return a C-compatible string without a mutable borrow.
17pub struct FmtBuf<'a> {
18 buf: &'a mut [u8],
19 pos: usize,
20}
21
22impl<'a> FmtBuf<'a> {
23 /// Create a new `FmtBuf` backed by `buf`.
24 ///
25 /// The usable capacity is `buf.len() - 1` because one byte is reserved for
26 /// the null terminator.
27 pub fn new(buf: &'a mut [u8]) -> Self {
28 if !buf.is_empty() {
29 buf[0] = 0;
30 }
31 Self { buf, pos: 0 }
32 }
33
34 /// Returns the formatted content as a byte slice (no null terminator).
35 pub fn as_bytes(&self) -> &[u8] {
36 &self.buf[..self.pos]
37 }
38
39 /// Returns the formatted content as a null-terminated byte slice,
40 /// suitable for passing to C APIs like LVGL.
41 pub fn as_cstr(&self) -> &[u8] {
42 // Null terminator is maintained by write_str after every write.
43 &self.buf[..self.pos + 1]
44 }
45}
46
47impl core::fmt::Write for FmtBuf<'_> {
48 fn write_str(&mut self, s: &str) -> core::fmt::Result {
49 let bytes = s.as_bytes();
50 let cap = self.buf.len().saturating_sub(1); // reserve 1 for null
51 let avail = cap.saturating_sub(self.pos);
52 let to_copy = bytes.len().min(avail);
53 self.buf[self.pos..self.pos + to_copy].copy_from_slice(&bytes[..to_copy]);
54 self.pos += to_copy;
55 self.buf[self.pos] = 0; // maintain null invariant
56 Ok(())
57 }
58}