Skip to content

Commit

Permalink
Merge pull request #2226 from SimonSapin/fmt-debug-hex
Browse files Browse the repository at this point in the history
Hexadecimal integers with fmt::Debug, including within larger types
  • Loading branch information
alexcrichton authored Feb 27, 2018
2 parents 27f4519 + 3f3b3a3 commit 16cfef8
Showing 1 changed file with 158 additions and 0 deletions.
158 changes: 158 additions & 0 deletions text/2226-fmt-debug-hex.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
- Feature Name: fmt-debug-hex
- Start Date: 2017-11-24
- RFC PR: https://github.com/rust-lang/rfcs/pull/2226
- Rust Issue: https://github.com/rust-lang/rust/issues/48584

# Summary
[summary]: #summary

Add support for formatting integers as hexadecimal with the `fmt::Debug` trait,
including when they occur within larger types.

```rust
println!("{:02X?}", b"AZaz\0")
```
```
[41, 5A, 61, 7A, 00]
```

# Motivation
[motivation]: #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
[guide-level-explanation]: #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:

```rust
let return_value = foo(bar);
let expected = &[ /* ... */ ][..];
assert!(return_value == expected, "{:08X?} != {:08X?}", return_value, expected);
```

# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation

## Formatting strings

The syntax of formatting strings
is [specified with a grammar](https://doc.rust-lang.org/std/fmt/#syntax)
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`:

```rust
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](https://github.com/rust-lang/rust/pull/46233).

# Drawbacks
[drawbacks]: #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
[alternatives]: #alternatives

Implementing `LowerHex` and `UpperHex` was proposed and rejected
in [PR #44751](https://github.com/rust-lang/rust/pull/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
[unresolved]: #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

0 comments on commit 16cfef8

Please # to comment.