Reverse byte order

This is a real world use case of how to reverse byte order.

It’s used in the code for Bitcoin.

According to the book “Mastering Bitcoin” this may have been an “unintentional consequence of a design decision in early Bitcoin software”

If you want to try this in a bash shell :

$ echo eb3ae38f27191aa5f3850dc9cad00492b88b72404f9da135698679268041c54a \
  | fold -w2 | tac | tr -d "\n"
4ac541802679866935a19d4f40728bb89204d0cac90d85f3a51a19278fe33aeb

Ok, so we know the problem, next, make a brew and then we’ll write some code:

Code

Here is the code to reverse the byte order, let’s analyse it

fn main() {
    let input = "eb3ae38f27191aa5f3850dc9cad00492b88b72404f9da135698679268041c54a";

    // Reverse the order of each byte
    let reversed = input
        .as_bytes()
        .chunks(2)
        .rev()
        .flat_map(|chunk| chunk.iter())
        .map(|&byte| byte as char)
        .collect::<String>();

    // Remove newline characters
    let result = reversed.replace("\n", "");

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

Let’s break down each part of the code:

  • as_bytes()
  • as_bytes() is a method available for strings in Rust that converts a string into a sequence of bytes. Each character in the string is represented by one or more bytes.
  • .chunks(2)
  • chunks(2) is a method provided by the Iterator trait in Rust. It transforms the sequence of bytes into an iterator of chunks, where each chunk contains 2 elements. In this case, it groups the bytes into pairs.
  • .rev()
  • rev() is another method from the Iterator trait. It reverses the order of elements in the iterator. So, after calling .rev(), the order of the pairs is reversed.
  • .flat_map(|chunk| chunk.iter())
  • flat_map is a method that both flattens and maps. In this context, it is used to flatten the iterator of pairs into a single iterator of bytes. The closure (|chunk| chunk.iter()) is applied to each pair, and iter() is used to iterate over the bytes within the pair.
  • .map(|&byte| byte as char)
  • map is another method from the Iterator trait. It transforms each element in the iterator. Here, it is used to convert each byte into a character. The |&byte| syntax is a closure that takes a reference to each byte and converts it to a char.

In summary, this chain of methods takes a hexadecimal string, converts it to a sequence of bytes, groups the bytes into pairs, reverses the order of the pairs, flattens them into a single sequence of bytes, and finally, converts each byte to a character. This process effectively reverses the order of each byte in the original string.

Step by Step

as_bytes()

chunks()

rev()

Note: We only saw the actual effect of as_bytes, chunks, and rev once we had used collect() at the end. (line 11)

So how does flat_map work?

fn main() {
    // Define a vector of vectors of integers
    let numbers = vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]];

    // Use flat_map to flatten the vector of vectors into integers
    let flattened_numbers: Vec<i32> = numbers
        .iter()
        .flat_map(|vec| vec.iter().cloned())
        .collect();

    // Print the original vector of vectors and the flattened vector of integers
    println!("Original numbers: {:?}", numbers);
    println!("Flattened integers: {:?}", flattened_numbers);
}
Original numbers: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Flattened integers: [1, 2, 3, 4, 5, 6, 7, 8, 9]

[Process exited 0]

Note the use of cloned() in our code?

The use of cloned() is often associated with flat_map when you want to work with owned values rather than references.

The flat_map method expects the closure passed to it to return an iterator, and it flattens the resulting iterators into a single iterator. If the original iterator yields references to values, using cloned() is a way to create a new iterator where each element is cloned, turning references into owned values. This is necessary because the resulting iterator from flat_map will combine elements from multiple iterators, and each element needs to have ownership.

In Rust, when you have a collection of references (&T), and you want to transform them into owned values (T), you often use the cloned() method on the iterator. This ensures that you get a new iterator containing owned copies of the original values.

Thanks for reading!

Previous article

Rye for Python

Next article

Arc::Clone