Commonly one wants to print a byte as 2 hex digits, for example 10
should give 0x0a
. In Rust one would use something like println!("{:#02x}", 10)
, but it gives 0xa
.
A list of things I've tried (using info from std::fmt
and fmt::LowerHex
and several StackOverflow questions):
println!("{:#02x}", 10);
println!("{:#0x}", 10);
println!("{:#x}", 10);
println!("{:#x?}", 10);
println!("{:#0x?}", 10);
println!("{:#02x?}", 10);
All of them output 0xa
, instead of the desired behavior of 0x0A
.
This question has been edited (removing relevant emotion), and subsequently marked as a duplicate of other questions. But other answers do not address why the list above doesn't work. The #
format option results in unexpected output (0xa
), and in particular the 02
, surprisingly, has no effect at all! No other question focusses on this, nor on #
in particular.
As documented in the std::fmt
module:
#
- This flag is indicates that the "alternate" form of printing should be used. The alternate forms are:
#x
- precedes the argument with a 0x#X
- precedes the argument with a 0x
This interacts with the request width, because the width accounts for the whole substitution and thus 0x0a
is 4 characters, not 2.
If you request a width that is less than the necessary width, the requested width is disregarded and the minimum width is used instead (here 3 characters).
A quick experiment on the playground:
fn main() {
println!("{:#01x}", 10);
println!("{:#02x}", 10);
println!("{:#03x}", 10);
println!("{:#04x}", 10);
println!("{:#05x}", 10);
println!("{:#06x}", 10);
}
Prints:
0xa
0xa
0xa
0x0a
0x00a
0x000a
All is well.
If you use:
println!("10 is 0x{:02x} in hex", 10); // ==> "0x0a"
Instead of:
println!("10 is {:#02x} in hex", 10); // "0xa"
It works.
However, as the commenters pointed out this is no bug, but confusion about the meaning of 02
. It does not mean the minimal number of significant digits, but the minimum total number of characters, which now must include 0x
. So this works also:
println!("10 is {:#04x} in hex", 10); // "0x0a"
But there are problems with this solution and the culture that promotes this as the "correct" solution.
First note that the inclusion of 0x
through #
promotes the concept of total width, which the 04
now specifies. One needs to mentally subtract 2 to get to the significant digits, which is the significant part. Regardless {:#04x}
will format 10000 as 0x2710
, which occupies 6 characters, not 4. So the concept of total width fails, as a concept, perhaps too trivially so.
In addition {:#03x}
, {:#02x}
and {:#01x}
confusingly all result in the same behaviour as `{:#x}, and are not flagged by the compiler (1.35) though they make no sense and indicate an error.
Secondly, the #
formatter is only useful for Rust-specific number representations, inserting for example 0x
but not $
(another common hex representation format).
Thirly, crucially, the concept of total width only becomes explicit when the #
formatter is used (and then silently fails in the common byte case). The #
formatter means Rust has the option to consider the total width, and one should use it when it applies (that is in the context of fixed-width layouts).
The remainder shows why #
is a special case solution, and why it is not recommended in the general case.
A formatter string should be chosen depending on the context, specifically how one thinks about displaying a number in that context, as the chosen format expresses intent. So:
{:#04x}
focuses on the total width, useful when doing a rigid fixed-width table layout. The #
formatter clearly is ergonomic in this case. {:#04x}
is also obviously useful when generating a custom formatting string (given a fixed-width context), for example when the output needs to be changed from hex to binary on demand (e.g. {:#04x}
to {:#04b}
), though it is hard to imagine concrete cases.
0x{02x}
expresses the number of significant digits, useful when the main concern is how to represent information on a line, given limited space. A common edit in this case is to remove the 0x
. With 0x{:02x}
the 0x
is simply removed, whereas with the {:#04}
formatter one would need to remove both the #
and substitute the 04
for 02
. The end result is the same in both cases: {:02x}
, but #
required a concept shift.
Depending on the case the #
formatter is ergonomic and expressive of intent, or it is not. Use the format that meshes with what you want to achieve. But in the general case it is probably best to regard #
as a relatively obscure feature. Its additional smartness is only desirable in well-defined cases.
Unfortunately other answers suggest {:#04x}
as the default, "correct" or the "Rust solution". However, the fact a certain feature (#
) is available doesn't mean that:
- Rust endorses it, or it is otherwise recommended to use it,
- the underlying concept of total width needs expressing, or
- the concept of total width should generalize to all of Rust format expressions (which is the root problem most answers demonstrate)
In summary Rust made #
available, but 0x{:02x}
is still often simpler, more to the point and universally recognized. It is KISS and doesn't have hidden defects.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With