- 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 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
Formatter
API