display_buffer 0.1.0

A helper library to make implementing `core::fmt::Display` easier
Documentation
#![no_std]
#![warn(rust_2018_idioms)]
#![doc = include_str!("../README.md")]

use core::mem::{self, MaybeUninit};
use core::{fmt, slice, str};

/// A buffer which can be used as a helper to implement [`core::fmt::Display`].
///
/// # Usage
///
/// ```
/// use core::fmt::{self, Write};
///
/// #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
/// pub struct Color {
///     pub red: u8,
///     pub green: u8,
///     pub blue: u8,
/// }
///
/// impl fmt::Display for Color {
///     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
///         if f.precision().is_none() && f.width().is_none() {
///             // When no formatting is required it is possible to format directly to `f`
///             return write!(f, "#{:02x}{:02x}{:02x}", self.red, self.green, self.blue);
///         }
///
///         let mut buf = display_buffer::DisplayBuffer::<7>::new();
///         write!(buf, "#{:02x}{:02x}{:02x}", self.red, self.green, self.blue)?;
///         f.pad(buf.as_str())
///     }
/// }
/// ```
pub struct DisplayBuffer<const SIZE: usize> {
    buf: [MaybeUninit<u8>; SIZE],
    len: u32,
}

impl<const SIZE: usize> DisplayBuffer<SIZE> {
    /// Create a new empty `DisplayBuffer`.
    ///
    /// # Panics
    ///
    /// Panics if `SIZE` is larger than [`u32::MAX`].
    pub const fn new() -> Self {
        if SIZE > u32::MAX as usize {
            panic!("SIZE has to be less then `u32::MAX`");
        }

        // SAFETY: An uninitialized [MaybeUninit<u8>; SIZE] is valid
        let buf = unsafe { MaybeUninit::uninit().assume_init() };
        Self { buf, len: 0 }
    }

    /// Returns the length of the formatted string, in bytes.
    pub const fn len(&self) -> usize {
        self.len as usize
    }

    /// Returns `true` if `self` has a length of zero bytes.
    pub const fn is_empty(&self) -> bool {
        self.len == 0
    }

    /// Extract a byte slice containing the entire formatted string.
    pub const fn as_bytes(&self) -> &[u8] {
        // SAFETY: `self.buf` is initialized up to `self.len` so casting it to a &[u8] is safe.
        unsafe { slice::from_raw_parts(&self.buf as *const _ as *const _, self.len()) }
    }

    /// Extract a string slice containing the entire formatted string.
    pub const fn as_str(&self) -> &str {
        // SAFETY: `self.buf` is only filled using the `fmt::Write` implementation so it always
        // contains a valid UTF-8 string.
        unsafe { str::from_utf8_unchecked(self.as_bytes()) }
    }

    fn unfilled(&mut self) -> &mut [MaybeUninit<u8>] {
        let len = self.len();
        &mut self.buf[len..]
    }
}

impl<const SIZE: usize> fmt::Write for DisplayBuffer<SIZE> {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        let unfilled = self.unfilled();
        let bytes = s.as_bytes();
        let bytes_len = bytes.len();

        if bytes_len > unfilled.len() {
            return Err(fmt::Error);
        }

        // SAFETY: The layout of `&[u8]` and `&[MaybeUninit<u8>]` is the same.
        let bytes: &[MaybeUninit<u8>] = unsafe { mem::transmute(bytes) };
        unfilled[..bytes_len].copy_from_slice(bytes);

        // No possible loss of precision because `bytes_len ≤ unfilled.len() ≤ SIZE ≤ u32::MAX`
        // and no possible overflow because
        // `self.len + bytes_len ≤ self.len + unfilled.len() = SIZE ≤ u32::MAX`.
        self.len += bytes_len as u32;

        Ok(())
    }

    fn write_char(&mut self, c: char) -> fmt::Result {
        let unfilled = self.unfilled();
        let char_len = c.len_utf8();

        if char_len > unfilled.len() {
            return Err(fmt::Error);
        }

        // SAFETY: The layout of `&[u8]` and `&[MaybeUninit<u8>]` is the same and
        // `char::encode_utf8` does not read from the slice we pass it, it only writes into it.
        c.encode_utf8(unsafe { mem::transmute(unfilled) });

        // No possible loss of precision because `char_len ∈ {1, 2, 3, 4}` and no possible overflow
        // because `self.len + char_len ≤ self.len + unfilled.len() = SIZE ≤ u32::MAX`.
        self.len += char_len as u32;

        Ok(())
    }
}

#[doc(hidden)]
#[inline]
pub fn __fmt<const SIZE: usize>(
    f: &mut fmt::Formatter<'_>,
    args: fmt::Arguments<'_>,
) -> fmt::Result {
    if f.precision().is_none() && f.width().is_none() {
        return f.write_fmt(args);
    }

    use fmt::Write;
    let mut buf = DisplayBuffer::<SIZE>::new();
    buf.write_fmt(args)?;
    f.pad(buf.as_str())
}

/// A helper macro which can be used to implement [`fmt::Display`] using a [`DisplayBuffer`].
///
/// # Usage
///
/// ```
/// use core::fmt;
///
/// #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
/// pub struct Color {
///     pub red: u8,
///     pub green: u8,
///     pub blue: u8,
/// }
///
/// impl fmt::Display for Color {
///     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
///         display_buffer::fmt!(f, 7, "#{:02x}{:02x}{:02x}", self.red, self.green, self.blue)
///     }
/// }
/// ```
#[macro_export]
macro_rules! fmt {
    ($f:expr, $size:expr, $($arg:tt)*) => {
        $crate::__fmt::<{ $size }>($f, format_args!($($arg)*))
    };
}