- Feature Name: fmt-debug-hex
- Start Date: 2017-11-24
- RFC PR: rust-lang/rfcs#2226
- Rust Issue: rust-lang/rust#48584
Summary
Add support for formatting integers as hexadecimal with the fmt::Debug trait,
including when they occur within larger types.
println!("{:02X?}", b"AZaz\0")
[41, 5A, 61, 7A, 00]
Motivation
Sometimes the bits that make up an integer are more meaningful than its purely numerical value.
For example, an RGBA color encoded in u32 with 8 bits per channel is easier to understand
when shown as 00CC44FF than 13387007.
The std::fmt::UpperHex and std::fmt::LowerHex traits provide hexadecimal formatting
through {:X} and {:x} in formatting strings,
but they’re only implemented for plain integer types
and not other types like slices that might contain integers.
The std::fmt::Debug trait (used with {:?}) however is intended for
formatting “in a programmer-facing, debugging context”.
It can be derived, and doing so is recommended for most types.
This RFC proposes adding the missing combination of:
- Output intended primarily for end-users (
Display) v.s. for programmers (Debug) - Numbers shown in decimal v.s. hexadecimal
Guide-level explanation
In formatting strings like in the format! and println! macros,
the formatting parameters x or X − to select lower-case or upper-case hexadecimal −
can now be combined with ? which select the Debug trait.
For example, format!("{:X?}", [65280].first()) returns Some(FF00).
This can also be combined with other formatting parameters.
For example, format!("{:02X?}", b"AZaz\0") zero-pads each byte to two hexadecimal digits
and return [41, 5A, 61, 7A, 00].
An API returning Vec<u32> might be tested like this:
let return_value = foo(bar);
let expected = &[ /* ... */ ][..];
assert!(return_value == expected, "{:08X?} != {:08X?}", return_value, expected);
Reference-level explanation
Formatting strings
The syntax of formatting strings is specified with a grammar which at the moment is as follows:
format_string := <text> [ maybe-format <text> ] *
maybe-format := '{' '{' | '}' '}' | <format>
format := '{' [ argument ] [ ':' format_spec ] '}'
argument := integer | identifier
format_spec := [[fill]align][sign]['#']['0'][width]['.' precision][type]
fill := character
align := '<' | '^' | '>'
sign := '+' | '-'
width := count
precision := count | '*'
type := identifier | ''
count := parameter | integer
parameter := argument '$'
This RFC adds an optional radix immediately before type:
format_spec := [[fill]align][sign]['#']['0'][width]['.' precision][radix][type]
radix: 'x' | 'X'
Formatter API
Note that x and X are already valid types.
They are only interpreted as a radix when the type is ?,
since combining them with other types doesn’t make sense.
This radix is exposed indirectly in two additional methods of std::fmt::Formatter:
impl<'a> Formatter<'a> {
// ...
/// Based on the radix and type: 16, 10, 8, or 2.
///
/// This is mostly useful in `Debug` impls,
/// where the trait itself doesn’t imply a radix.
fn number_radix(&self) -> u32
/// true for `X` or `E`
///
/// This is mostly useful in `Debug` impls,
/// where the trait itself doesn’t imply a case.
fn number_uppercase(&self) -> bool
}
Although the radix and type are separate in the formatting string grammar, they are intentionally conflated in this new API.
Debug impls
The Debug implementation for primitive integer types {u,i}{8,16,32,64,128,size}
is modified to defer to LowerHex or UpperHex instead of Display,
based on formatter.number_radix() and formatter.number_uppercase().
The alternate # flag is ignored, since it already has a separate meaning for Debug:
the 0x prefix is not included.
As of Rust 1.22, impls using the Formatter::debug_* methods do not forward
formatting parameters such as width when formatting keys/values/items.
Doing so is important for this RFC to be useful.
This is fixed by PR #46233.
Drawbacks
The hexadecimal flag in the the Debug trait is superficially redundant
with the LowerHex and UpperHex traits.
If these traits were not stable yet, we could have considered a more unified design.
Rationale and alternatives
Implementing LowerHex and UpperHex was proposed and rejected
in PR #44751.
The status quo is that debugging or testing code that could be a one-liner
requires manual Debug impls and/or concatenating the results of separate
string formatting operations.
Unresolved questions
- Should this be extended to octal and binary (as
{:o?}and{:b?})? Other formatting types/traits too? - Details of the new
FormatterAPI