Compile-time string formatting


Keywords
concat, format, no-std, macros, rust
License
Zlib

Documentation

Rust crates-io api-docs

Compile-time string formatting.

This crate provides types and macros for formatting strings at compile-time.

Rust versions

There are some features that require a variety of Rust versions, the sections below describe the features that are available for each version.

Rust 1.57.0

These macros are available in Rust 1.57.0:

  • concatcp: Concatenates integers, bool, char, and &str constants into a &'static str constant.

  • formatcp: format-like formatting which takes integers, bool, char, and &str constants, and emits a &'static str constant.

  • str_get: Indexes a &'static str constant, returning None when the index is out of bounds.

  • str_index: Indexes a &'static str constant.

  • str_repeat: Creates a &'static str by repeating a &'static str constant times times.

  • str_splice: Replaces a substring in a &'static str constant.

  • map_ascii_case: Converts a &'static str constant to a different casing style, determined by a Case argument.

  • str_replace: Replaces all the instances of a pattern in a &'static str constant with another &'static str constant.

The "assertcp" feature enables the assertcp, assertcp_eq, and assertcp_ne macros. These macros are like the standard library assert macros, but evaluated at compile-time, with the limitation that they can only have primitive types as arguments (just like concatcp and formatcp).

Rust 1.64.0

The "rust_1_64" feature enables these macros:

Rust 1.83.0

By enabling the "fmt" feature, you can use a std::fmt-like API.

This requires Rust 1.83.0, because it uses mutable references in const fn.

All the other features of this crate are implemented on top of the const_format::fmt API:

  • concatc: Concatenates many standard library and user defined types into a &'static str constant.

  • formatc: format-like macro that can format many standard library and user defined types into a &'static str constant.

  • writec: write-like macro that can format many standard library and user defined types into a type that implements WriteMarker.

The "derive" feature enables the ConstDebug macro, and the "fmt" feature.
ConstDebug derives the FormatMarker trait, and implements an inherent const_debug_fmt method for compile-time debug formatting.

The "assertc" feature enables the assertc, assertc_eq, assertc_ne macros, and the "fmt" feature.
These macros are like the standard library assert macros, but evaluated at compile-time.

Examples

Concatenation of primitive types

use const_format::concatcp;

const NAME: &str = "Bob";
const FOO: &str = concatcp!(NAME, ", age ", 21u8,"!");

assert_eq!(FOO, "Bob, age 21!");

Formatting primitive types

use const_format::formatcp;

const NAME: &str = "John";

const FOO: &str = formatcp!("{NAME}, age {}!", compute_age(NAME));

assert_eq!(FOO, "John, age 24!");

const fn compute_age(s: &str) -> usize { s.len() * 6 }

Formatting custom types

This example demonstrates how you can use the ConstDebug derive macro, and then format the type into a &'static str constant.

This example requires Rust 1.83.0, and the "derive" feature.

use const_format::{ConstDebug, formatc};

#[derive(ConstDebug)]
struct Message{
    ip: [Octet; 4],
    value: &'static str,
}

#[derive(ConstDebug)]
struct Octet(u8);

const MSG: Message = Message{
    ip: [Octet(127), Octet(0), Octet(0), Octet(1)],
    value: "Hello, World!",
};

const FOO: &str = formatc!("{:?}", MSG);

assert_eq!(
    FOO,
    "Message { ip: [Octet(127), Octet(0), Octet(0), Octet(1)], value: \"Hello, World!\" }"
);

Formatted const assertions

This example demonstrates how you can use the assertcp_ne macro to do compile-time inequality assertions with formatted error messages.

This requires the "assertcp" feature.

use const_format::assertcp_ne;

macro_rules! check_valid_pizza{
    ($user:expr, $topping:expr) => {
        assertcp_ne!(
            $topping,
            "pineapple",
            "You can't put pineapple on pizza, {}",
            $user,
        );
    }
}

check_valid_pizza!("John", "salami");
check_valid_pizza!("Dave", "sausage");
check_valid_pizza!("Bob", "pineapple");

This is the compiler output:

error[E0080]: evaluation of constant value failed
  --> src/lib.rs:178:27
   |
20 | check_valid_pizza!("Bob", "pineapple");
   |                           ^^^^^^^^^^^ the evaluated program panicked at '
assertion failed: `(left != right)`
 left: `"pineapple"`
right: `"pineapple"`
You can't put pineapple on pizza, Bob
', src/lib.rs:20:27


Limitations

All of the macros from const_format have these limitations:

  • The formatting macros that expand to &'static strs can only use constants from concrete types, so while a Type::<u8>::FOO argument would be fine, Type::<T>::FOO would not be (T being a type parameter).

  • Integer arguments must have a type inferrable from context, more details in the Integer arguments section.

  • They cannot be used places that take string literals. So #[doc = "foobar"] cannot be replaced with #[doc = concatcp!("foo", "bar") ].

Integer arguments

Integer arguments must have a type inferrable from context. so if you only pass an integer literal it must have a suffix.

Example of what does compile:

const N: u32 = 1;
assert_eq!(const_format::concatcp!(N + 1, 2 + N), "23");

assert_eq!(const_format::concatcp!(2u32, 2 + 1u8, 3u8 + 1), "234");

Example of what does not compile:

assert_eq!(const_format::concatcp!(1 + 1, 2 + 1), "23");

Plans

None right now.

Renaming crate

All function-like macros from const_format can be used when the crate is renamed.

The ConstDebug derive macro has the #[cdeb(crate = "foo::bar")] attribute to tell it where to find the const_format crate.

Example of renaming the const_format crate in the Cargo.toml file:

[dependencies]
cfmt = {version = "0.*", package = "const_format"}

Cargo features

  • "fmt": Enables the std::fmt-like API and "rust_1_83" feature, requires Rust 1.83.0 because it uses mutable references in const fn.
    This feature includes the formatc/writec formatting macros.

  • "derive": requires Rust 1.83.0, implies the "fmt" feature, provides the ConstDebug derive macro to format user-defined types at compile-time.
    This implicitly uses the syn crate, so clean compiles take a bit longer than without the feature.

  • "assertc": requires Rust 1.83.0, implies the "fmt" feature, enables the assertc, assertc_eq, and assertc_ne assertion macros.
    This feature was previously named "assert", but it was renamed to avoid confusion with the "assertcp" feature.

  • "assertcp": Enables the assertcp, assertcp_eq, and assertcp_ne assertion macros.

  • "rust_1_64": Enables the str_split macro. Allows the as_bytes_alt methods and slice_up_to_len_alt methods to run in constant time, rather than linear time (proportional to the truncated part of the slice).

  • "rust_1_83": Enables the "rust_1_64" feature and makes macros that evaluate to a value compatible with inline const patterns.

No-std support

const_format is unconditionally #![no_std], it can be used anywhere Rust can be used.

Minimum Supported Rust Version

const_format requires Rust 1.57.0.

Features that require newer versions of Rust, or the nightly compiler, need to be explicitly enabled with cargo features.