|
| 1 | +- Feature Name: fmt-debug-hex |
| 2 | +- Start Date: 2017-11-24 |
| 3 | +- RFC PR: https://github.com/rust-lang/rfcs/pull/2226 |
| 4 | +- Rust Issue: https://github.com/rust-lang/rust/issues/48584 |
| 5 | + |
| 6 | +# Summary |
| 7 | +[summary]: #summary |
| 8 | + |
| 9 | +Add support for formatting integers as hexadecimal with the `fmt::Debug` trait, |
| 10 | +including when they occur within larger types. |
| 11 | + |
| 12 | +```rust |
| 13 | +println!("{:02X?}", b"AZaz\0") |
| 14 | +``` |
| 15 | +``` |
| 16 | +[41, 5A, 61, 7A, 00] |
| 17 | +``` |
| 18 | + |
| 19 | +# Motivation |
| 20 | +[motivation]: #motivation |
| 21 | + |
| 22 | +Sometimes the bits that make up an integer are more meaningful than its purely numerical value. |
| 23 | +For example, an RGBA color encoded in `u32` with 8 bits per channel is easier to understand |
| 24 | +when shown as `00CC44FF` than `13387007`. |
| 25 | + |
| 26 | +The `std::fmt::UpperHex` and `std::fmt::LowerHex` traits provide hexadecimal formatting |
| 27 | +through `{:X}` and `{:x}` in formatting strings, |
| 28 | +but they’re only implemented for plain integer types |
| 29 | +and not other types like slices that might contain integers. |
| 30 | + |
| 31 | +The `std::fmt::Debug` trait (used with `{:?}`) however is intended for |
| 32 | +formatting “in a programmer-facing, debugging context”. |
| 33 | +It can be derived, and doing so is recommended for most types. |
| 34 | + |
| 35 | +This RFC proposes adding the missing combination of: |
| 36 | + |
| 37 | +* Output intended primarily for end-users (`Display`) v.s. for programmers (`Debug`) |
| 38 | +* Numbers shown in decimal v.s. hexadecimal |
| 39 | + |
| 40 | +# Guide-level explanation |
| 41 | +[guide-level-explanation]: #guide-level-explanation |
| 42 | + |
| 43 | +In formatting strings like in the `format!` and `println!` macros, |
| 44 | +the formatting parameters `x` or `X` − to select lower-case or upper-case hexadecimal − |
| 45 | +can now be combined with `?` which select the `Debug` trait. |
| 46 | + |
| 47 | +For example, `format!("{:X?}", [65280].first())` returns `Some(FF00)`. |
| 48 | + |
| 49 | +This can also be combined with other formatting parameters. |
| 50 | +For example, `format!("{:02X?}", b"AZaz\0")` zero-pads each byte to two hexadecimal digits |
| 51 | +and return `[41, 5A, 61, 7A, 00]`. |
| 52 | + |
| 53 | +An API returning `Vec<u32>` might be tested like this: |
| 54 | + |
| 55 | +```rust |
| 56 | +let return_value = foo(bar); |
| 57 | +let expected = &[ /* ... */ ][..]; |
| 58 | +assert!(return_value == expected, "{:08X?} != {:08X?}", return_value, expected); |
| 59 | +``` |
| 60 | + |
| 61 | +# Reference-level explanation |
| 62 | +[reference-level-explanation]: #reference-level-explanation |
| 63 | + |
| 64 | +## Formatting strings |
| 65 | + |
| 66 | +The syntax of formatting strings |
| 67 | +is [specified with a grammar](https://doc.rust-lang.org/std/fmt/#syntax) |
| 68 | +which at the moment is as follows: |
| 69 | + |
| 70 | +``` |
| 71 | +format_string := <text> [ maybe-format <text> ] * |
| 72 | +maybe-format := '{' '{' | '}' '}' | <format> |
| 73 | +format := '{' [ argument ] [ ':' format_spec ] '}' |
| 74 | +argument := integer | identifier |
| 75 | + |
| 76 | +format_spec := [[fill]align][sign]['#']['0'][width]['.' precision][type] |
| 77 | +fill := character |
| 78 | +align := '<' | '^' | '>' |
| 79 | +sign := '+' | '-' |
| 80 | +width := count |
| 81 | +precision := count | '*' |
| 82 | +type := identifier | '' |
| 83 | +count := parameter | integer |
| 84 | +parameter := argument '$' |
| 85 | +``` |
| 86 | + |
| 87 | +This RFC adds an optional *radix* immediately before *type*: |
| 88 | + |
| 89 | +``` |
| 90 | +format_spec := [[fill]align][sign]['#']['0'][width]['.' precision][radix][type] |
| 91 | +radix: 'x' | 'X' |
| 92 | +``` |
| 93 | + |
| 94 | +## `Formatter` API |
| 95 | + |
| 96 | +Note that `x` and `X` are already valid *types*. |
| 97 | +They are only interpreted as a radix when the type is `?`, |
| 98 | +since combining them with other types doesn’t make sense. |
| 99 | + |
| 100 | +This radix is exposed indirectly in two additional methods of `std::fmt::Formatter`: |
| 101 | + |
| 102 | +```rust |
| 103 | +impl<'a> Formatter<'a> { |
| 104 | + // ... |
| 105 | + |
| 106 | + /// Based on the radix and type: 16, 10, 8, or 2. |
| 107 | + /// |
| 108 | + /// This is mostly useful in `Debug` impls, |
| 109 | + /// where the trait itself doesn’t imply a radix. |
| 110 | + fn number_radix(&self) -> u32 |
| 111 | + |
| 112 | + /// true for `X` or `E` |
| 113 | + /// |
| 114 | + /// This is mostly useful in `Debug` impls, |
| 115 | + /// where the trait itself doesn’t imply a case. |
| 116 | + fn number_uppercase(&self) -> bool |
| 117 | +} |
| 118 | +``` |
| 119 | + |
| 120 | +Although the radix and type are separate in the formatting string grammar, |
| 121 | +they are intentionally conflated in this new API. |
| 122 | + |
| 123 | +## `Debug` impls |
| 124 | + |
| 125 | +The `Debug` implementation for primitive integer types `{u,i}{8,16,32,64,128,size}` |
| 126 | +is modified to defer to `LowerHex` or `UpperHex` instead of `Display`, |
| 127 | +based on `formatter.number_radix()` and `formatter.number_uppercase()`. |
| 128 | +The *alternate* `#` flag is ignored, since it already has a separate meaning for `Debug`: |
| 129 | +the `0x` prefix is *not* included. |
| 130 | + |
| 131 | +As of Rust 1.22, impls using the `Formatter::debug_*` methods do not forward |
| 132 | +formatting parameters such as *width* when formatting keys/values/items. |
| 133 | +Doing so is important for this RFC to be useful. |
| 134 | +This is fixed by [PR #46233](https://github.com/rust-lang/rust/pull/46233). |
| 135 | + |
| 136 | +# Drawbacks |
| 137 | +[drawbacks]: #drawbacks |
| 138 | + |
| 139 | +The hexadecimal flag in the the `Debug` trait is superficially redundant |
| 140 | +with the `LowerHex` and `UpperHex` traits. |
| 141 | +If these traits were not stable yet, we could have considered a more unified design. |
| 142 | + |
| 143 | +# Rationale and alternatives |
| 144 | +[alternatives]: #alternatives |
| 145 | + |
| 146 | +Implementing `LowerHex` and `UpperHex` was proposed and rejected |
| 147 | +in [PR #44751](https://github.com/rust-lang/rust/pull/44751). |
| 148 | + |
| 149 | +The status quo is that debugging or testing code that could be a one-liner |
| 150 | +requires manual `Debug` impls and/or concatenating the results of separate |
| 151 | +string formatting operations. |
| 152 | + |
| 153 | +# Unresolved questions |
| 154 | +[unresolved]: #unresolved-questions |
| 155 | + |
| 156 | +* Should this be extended to octal and binary (as `{:o?}` and `{:b?}`)? |
| 157 | + Other formatting types/traits too? |
| 158 | +* Details of the new `Formatter` API |
0 commit comments