Crate ufmt

source ·
Expand description

μfmt, a (6-40x) smaller, (2-9x) faster and panic-free alternative to core::fmt

Design goals

From highest priority to lowest priority

  • Optimized for binary size and speed (rather than for compilation time)
  • No dynamic dispatch in generated code
  • No panicking branches in generated code, when optimized
  • No recursion where possible

Features

Non-features

These are out of scope

  • Padding, alignment and other formatting options
  • Formatting floating point numbers

Examples

  • uwrite! / uwriteln!
use ufmt::{derive::uDebug, uwrite};

#[derive(uDebug)]
struct Pair { x: u32, y: u32 }

let mut s = String::new();
let pair = Pair { x: 1, y: 2 };
uwrite!(s, "{:?}", pair).unwrap();
assert_eq!(s, "Pair { x: 1, y: 2 }");
  • Hexadecimal formatting

Lowercase ({:x}), uppercase ({:X}), 0x-prefix ({:#x}) and padding ({:02x}) are supported on primitive integer types.

use ufmt::uwrite;

let mut s = String::new();
uwrite!(s, "{:#06x}", 0x42);
assert_eq!(s, "0x0042");
  • implementing uWrite

When implementing the uWrite trait you should prefer the ufmt_write::uWrite crate over the ufmt::uWrite crate for better forward compatibility.

use core::convert::Infallible;

use ufmt_write::uWrite;

struct MyWriter;

impl uWrite for MyWriter {
    type Error = Infallible;

    fn write_str(&mut self, s: &str) -> Result<(), Self::Error> {
        // ..
        Ok(())
    }
}
  • writing a macro_rules! macro that uses uwrite! (or uwriteln!).
// like `std::format!` it returns a `std::String` but uses `uwrite!` instead of `write!`
macro_rules! uformat {
    // IMPORTANT use `tt` fragments instead of `expr` fragments (i.e. `$($exprs:expr),*`)
    ($($tt:tt)*) => {{
        let mut s = String::new();
        match ufmt::uwrite!(&mut s, $($tt)*) {
            Ok(_) => Ok(s),
            Err(e) => Err(e),
        }
    }}
}

Benchmarks

The benchmarks ran on a ARM Cortex-M3 chip (thumbv7m-none-eabi).

The benchmarks were compiled with nightly-2019-05-01, -C opt-level=3, lto = true, codegen-units = 1.

In all benchmarks x = i32::MIN and y = i32::MIN plus some obfuscation was applied to prevent const-propagation of the *write! arguments.

The unit of time is one core clock cycle: 125 ns (8 MHz)

The .text and .rodata columns indicate the delta (in bytes) when commenting out the *write! statement.

CodeTime%.text%.rodata%
write!("Hello, world!")154~1906~248~
uwrite!("Hello, world!")2013.0%341.8%166.5%
write!(w, "{}", 0i32)256~1958~232~
uwrite!(w, "{}", 0i32)3714.5%28814.7%00%
write!(w, "{}", x)381~
uwrite!(w, "{}", x)29577.4%
write!(w, "{:?}", Pair { x: 0, y: 0 })996~4704~312~
uwrite!(w, "{:?}", Pair { x: 0, y: 0 })25425.5%75216.0%247.7%
write!(w, "{:?}", Pair { x, y })1264~
uwrite!(w, "{:?}", Pair { x, y })77661.4%
write!(w, "{:#?}", Pair { x: 0, y: 0 })2853~4710~348~
uwrite!(w, "{:#?}", Pair { x: 0, y: 0 })30110.6%75416.0%246.9%
write!(w, "{:#?}", Pair { x, y })3693~
uwrite!(w, "{:#?}", Pair { x, y })82322.3%

Benchmark program:

static X: AtomicI32 = AtomicI32::new(i32::MIN); // or `0`
static Y: AtomicI32 = AtomicI32::new(i32::MIN); // or `0`

#[exception]
fn PendSV() {
   // read DWT.CYCCNT here

   let x = X.load(Ordering::Relaxed);
   let y = Y.load(Ordering::Relaxed);

   let p = Pair { x, y };

   uwrite!(&mut W, "{:#?}", p).ok();

   // write!(&mut W, "{:#?}", p).ok();

   asm::bkpt(); // read DWT.CYCCNT here
}

Writer used in the benchmarks:

use core::{convert::Infallible, fmt, ptr};

use ufmt::uWrite;

struct W;

impl uWrite for W {
    type Error = Infallible;

    fn write_str(&mut self, s: &str) -> Result<(), Infallible> {
        s.as_bytes()
            .iter()
            .for_each(|b| unsafe { drop(ptr::read_volatile(b)) });

        Ok(())
    }
}

impl fmt::Write for W {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        s.as_bytes()
            .iter()
            .for_each(|b| unsafe { drop(ptr::read_volatile(b)) });

        Ok(())
    }
}

Minimum Supported Rust Version (MSRV)

This crate does not have a Minimum Supported Rust Version (MSRV) and may make use of language features and API in the standard library available in the latest stable Rust version.

In other words, changes in the Rust version requirement of this crate are not considered semver breaking change and may occur in patch version release.

Modules

Macros

  • Write formatted data into a buffer
  • Write formatted data into a buffer, with a newline appended

Structs

Traits

  • Just like core::fmt::Debug
  • Just like core::fmt::Display
  • A collection of methods that are required / used to format a message into a stream.