Runtime-fmt, a runtime formatting crate

— Rust

runtime-fmt is a small, nightly-only, safe Rust crate which provides variants of the six standard formatting macros which validate their format strings at runtime. It has feature parity with std::fmt and accepts exactly the same syntax for format strings and arguments, with the additional feature of supporting format strings which are not known at compile time.

#[macro_use] extern crate runtime_fmt;

fn main() {
	let format_string = "Hello, {}!";
	rt_println!(format_string, "world").unwrap();
	rt_println!("bogus {} reference").unwrap_err();
	rt_println!("bogus }{ format string").unwrap_err();
}

Applications include internationalization, configuration, and modding. For example, designers might be allowed to adjust text with interspersed values in a video game, or command-line users could specify what shape the output should be.

The core implementation is under 300 SLOC, plus 550 or so to embed libfmt_macros.

Implementation

runtime-fmt is safe. It achieves direct feature-parity with the standard formatting macros by using the same code that rustc uses to parse format strings. Somewhat awkwardly, this code is included inline in the crate rather than linked in, because it is distributed only dynamically, and most Rust users don’t want to link libstd dynamically. On top of format string parsing, rustc includes elaborate machinery for converting the parsed format string into an AST for macro expansion. Because runtime-fmt does not need to work on the AST level, and can make assumptions about the values returned by libfmt_macros, this conversion is vastly simplified.

All format specifiers are supported. Recognizing at runtime whether a given argument supports a given format string is achieved through specialization, implementing internal shadow traits of the standard formatting traits which return None when unavailable, and surfacing this as an error to users.

All errors are encapsulated in an enum Error, returned within a Result from all the macros. Syntax errors are returned as-is, including descriptions similar to those generated by rustc, and other types of error variants are available, including std::io::Error and std::fmt::Error variants which may be returned when using rt_write! or rt_writeln!.

The crate requires nightly for obvious reasons; access to the formatting machinery isn’t stable, and might never be. That said, it changes rarely, and the crate is unlikely to break often. The use of specialization described earlier also requires nightly.

Test Suite

This crate passes the main body of tests from rustc’s test suite, as well as a small smattering of tests written specifically for runtime-fmt. I intend to continue to expand the test suite, both to include more tests from rustc and to more rigorously ensure that format string and argument combinations which ought to error do.

Future Expansion

Some performance is left on the table in the initial implementation. Particularly, format strings are re-parsed each time the string is to be formatted. I have an idea on how to fix this, revolving around specifying a struct or tuple type in rt_format_args! once during parsing, where the format string is parsed and types are resolved and checked, and then supplying values of that struct type which can be formatted quickly by the prebuilt formatter.

Most of std::fmt is also available in core. The library currently uses heap allocation in many instances, but it may be possible to adapt it to use static buffers or other trickery in those situations and thereby allow the library to be used under #[no_std], just like the builtin formatting macros.

Addendum: thanks illegalprime on IRC for pointing out that the snippet at the top of the blog post didn’t demonstrate error handling.