75

I need to convert &[u8] to a hex representation. For example [ A9, 45, FF, 00 ... ].

The trait std::fmt::UpperHex is not implemented for slices (so I can't use std::fmt::format). Rust has the serialize::hex::ToHex trait, which converts &[u8] to a hex String, but I need a representation with separate bytes.

I can implement trait UpperHex for &[u8] myself, but I'm not sure how canonical this would be. What is the most canonical way to do this?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Aleksandr
  • 1,303
  • 2
  • 11
  • 19
  • When you say a "hex representation", do you mean you want a string in the end? Or what type? – Shepmaster Dec 25 '14 at 20:03
  • 2
    BTW, you can't implement `UpperHex` for `&[u8]`, because both the trait and the type are not "yours" (neither of them is defined in your crate). You can implement `UpperHex` for a newtype for `&[u8]` (e.g. `struct HexSlice<'a>(&'a [u8])`, but it will likely be inconvenient. Defining a simple function is better. – Vladimir Matveev Dec 25 '14 at 21:03
  • 1
    Vladimir Matveev, I meant create new type, wich contains [u8] and use it. Thx for answer. – Aleksandr Dec 26 '14 at 06:19

5 Answers5

149

Rust 1.26.0 and up

The :x? "debug with hexadecimal integers" formatter can be used:

let data = b"hello";
// lower case
println!("{:x?}", data);
// upper case
println!("{:X?}", data);

let data = [0x0, 0x1, 0xe, 0xf, 0xff];
// print the leading zero
println!("{:02X?}", data);
// It can be combined with the pretty modifier as well
println!("{:#04X?}", data);

Output:

[68, 65, 6c, 6c, 6f]
[68, 65, 6C, 6C, 6F]
[00, 01, 0E, 0F, FF]
[
    0x00,
    0x01,
    0x0E,
    0x0F,
    0xFF,
]

If you need more control or need to support older versions of Rust, keep reading.

Rust 1.0 and up

use std::fmt::Write;

fn main() {
    let mut s = String::new();
    for &byte in "Hello".as_bytes() {
        write!(&mut s, "{:X} ", byte).expect("Unable to write");
    }

    println!("{}", s);
}

This can be fancied up by implementing one of the formatting traits (fmt::Debug, fmt::Display, fmt::LowerHex, fmt::UpperHex, etc.) on a wrapper struct and having a little constructor:

use std::fmt;

struct HexSlice<'a>(&'a [u8]);

impl<'a> HexSlice<'a> {
    fn new<T>(data: &'a T) -> HexSlice<'a>
    where
        T: ?Sized + AsRef<[u8]> + 'a,
    {
        HexSlice(data.as_ref())
    }
}

// You can choose to implement multiple traits, like Lower and UpperHex
impl fmt::Display for HexSlice<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for byte in self.0 {
            // Decide if you want to pad the value or have spaces inbetween, etc.
            write!(f, "{:X} ", byte)?;
        }
        Ok(())
    }
}

fn main() {
    // To get a `String`
    let s = format!("{}", HexSlice::new("Hello"));

    // Or print it directly
    println!("{}", HexSlice::new("world"));

    // Works with
    HexSlice::new("Hello"); // string slices (&str)
    HexSlice::new(b"Hello"); // byte slices (&[u8])
    HexSlice::new(&"World".to_string()); // References to String
    HexSlice::new(&vec![0x00, 0x01]); // References to Vec<u8>
}

You can be even fancier and create an extension trait:

trait HexDisplayExt {
    fn hex_display(&self) -> HexSlice<'_>;
}

impl<T> HexDisplayExt for T
where
    T: ?Sized + AsRef<[u8]>,
{
    fn hex_display(&self) -> HexSlice<'_> {
        HexSlice::new(self)
    }
}

fn main() {
    println!("{}", "world".hex_display());
}
Stargateur
  • 24,473
  • 8
  • 65
  • 91
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 8
    Shouldn't the format string be `"{:02X}"` to ensure that two hex characters are printed for each character (including the first `0` if needed)? – phoenix Sep 09 '17 at 19:02
  • 2
    @phoenix yep, if that's what is needed. That's what I mean by the comment "Decide if you want to pad out the value here". – Shepmaster Sep 11 '17 at 12:21
  • You need `"{:02x?}"`. It still prints it as a comma separated list though. :-/ – Timmmm Mar 05 '20 at 16:16
  • Yes, reminder to people you need to zero pad with `{:02X?}` if you're going to pasting the resulting string to a hex editor etc. Just wasted a bunch of time figuring out why I got garbage values. – Miscreant Nov 07 '20 at 02:14
18

Use hex::encode from the hex crate.

let a: [u8;4] = [1, 3, 3, 7];
assert_eq!(hex::encode(&a), "01030307");
[dependencies]
hex = "0.4"
Westy92
  • 19,087
  • 4
  • 72
  • 54
leongross
  • 329
  • 2
  • 8
10

Since the accepted answer doesn't work on Rust 1.0 stable, here's my attempt. Should be allocationless and thus reasonably fast. This is basically a formatter for [u8], but because of the coherence rules, we must wrap [u8] to a self-defined type ByteBuf(&[u8]) to use it:

struct ByteBuf<'a>(&'a [u8]);

impl<'a> std::fmt::LowerHex for ByteBuf<'a> {
    fn fmt(&self, fmtr: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        for byte in self.0 {
            fmtr.write_fmt(format_args!("{:02x}", byte))?;
        }
        Ok(())
    }
}

Usage:

let buff = [0_u8; 24];
println!("{:x}", ByteBuf(&buff));
Caesar
  • 6,733
  • 4
  • 38
  • 44
GolDDranks
  • 3,272
  • 4
  • 22
  • 30
7

I'm doing it this way:

let bytes : Vec<u8> = "привет".to_string().as_bytes().to_vec();
let hex : String = bytes.iter()
  .map(|b| format!("{:02x}", b).to_string())
  .collect::<Vec<String>>()
  .join(" ");
yegor256
  • 102,010
  • 123
  • 446
  • 597
6

There's a crate for this: hex-slice.

For example:

extern crate hex_slice;
use hex_slice::AsHex;

fn main() {
    let foo = vec![0u32, 1, 2 ,3];
    println!("{:02x}", foo.as_hex());
}
Gabriel
  • 1,262
  • 12
  • 12
  • Underrated comment! Thanks, there's also the https://docs.rs/bytes/1.4.0/bytes/struct.Bytes.html which implements LowerHex but hex-slice is much more lightweight. – Eduard Nicodei Apr 09 '23 at 15:45