Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I convert two u8 primitives into a u16 primitive?

Tags:

rust

I'm reading a binary file into a Rust program using a Vec<u8> as a buffer. Two bytes in the stream represent a big-endian u16.

So far, the only way I've figured out how to convert to a primitive u16 involves converting the two elements to Strings first, and it looks terrible.

Code:

let vector: Vec<u8> = [1, 16].to_vec();
let vector0: String = format!("{:02x}", vector[0]);
let vector1: String = format!("{:02x}", vector[1]);
let mut vector_combined = String::new();
vector_combined = vector_combined + &vector0.clone();
vector_combined = vector_combined + &vector1.clone();
let number: u16 = u16::from_str_radix(&vector_combined.to_string(), 16).unwrap();

println!("vector[0]: 0x{:02x}", vector[0]);
println!("vector[1]: 0x{:02x}", vector[1]);
println!("number: 0x{:04x}", number);

Output:

vector[0]: 0x01
vector[1]: 0x10
number: 0x0110
like image 417
Hydraxan14 Avatar asked May 09 '18 00:05

Hydraxan14


2 Answers

If you actually had two distinct u8s, the conventional solution involves bitwise manipulation, specifically shifting and bitwise OR. This requires zero heap allocation and is very efficient:

let number = ((vector[0] as u16) << 8) | vector[1] as u16;

And a graphical explanation:

             A0                   B0
        +--------+           +--------+
        |XXXXXXXX|           |YYYYYYYY|
        +-------++           +-------++
                |                    |
 A1 = A0 as u16 |     B1 = B0 as u16 |
+---------------v+   +---------------v+
|00000000XXXXXXXX|   |00000000YYYYYYYY|
+---------------++   +---------------++
                |                    |
   A2 = A1 << 8 |                    |
+---------------v+                   |
|XXXXXXXX00000000|                   |
+---------------++                   |
                |              +--+  |
                +-------------->OR<--+
                               +-++
                                 |
                     V = A2 | B1 |
                 +----------+----v+
                 |XXXXXXXXYYYYYYYY|
                 +----------------+

However, you are really looking at your problem too narrowly. You don't have two u8, you have a &[u8].

In this case, use the byteorder crate:

use byteorder::{ByteOrder, LittleEndian}; // 1.3.4

fn main() {
    let data = [1, 16];
    let v = LittleEndian::read_u16(&data);
    println!("{}", v);
}

This shows its power when you want to handle reading through the buffer:

use byteorder::{BigEndian, LittleEndian, ReadBytesExt}; // 1.3.4

fn main() {
    let data = [1, 16, 1, 2];
    let mut current = &data[..];

    let v1 = current.read_u16::<LittleEndian>();
    let v2 = current.read_u16::<BigEndian>();

    println!("{:?}, {:?}", v1, v2); // Ok(4097), Ok(258)
}

As you can see, you need to be conscious of the endianness of your input data.

You could also get a fixed-size array from your slice and then use u16::from_le_bytes. If you had a &[u8] and wanted to get a Vec<u16>, you can iterate over appropriately-sized slices using chunks_exact (or array_chunks).

See also:

  • How do you set, clear and toggle a single bit in Rust?
  • Temporarily transmute [u8] to [u16]
  • How do I convert a Vec<T> to a Vec<U> without copying the vector?

Free code review on your original post:

  • There's no need to use to_vec here, use vec! instead.

  • There's no need to specify the vast majority of the types.

    let vector = [1u8, 16].to_vec();
    
    let vector0 = format!("{:02x}", vector[0]);
    let vector1 = format!("{:02x}", vector[1]);
    let mut vector_combined = String::new();
    vector_combined = vector_combined + &vector0.clone();
    vector_combined = vector_combined + &vector1.clone();
    let number = u16::from_str_radix(&vector_combined.to_string(), 16).unwrap();
    
  • There's no need to clone the strings before taking a reference to them when adding.

  • There's no need to convert the String to... another String in from_str_radix.

    let vector0 = format!("{:02x}", vector[0]);
    let vector1 = format!("{:02x}", vector[1]);
    let mut vector_combined = String::new();
    vector_combined = vector_combined + &vector0;
    vector_combined = vector_combined + &vector1;
    let number = u16::from_str_radix(&vector_combined, 16).unwrap();
    
  • There's no need to create an empty String to append to, just use vector0

    let vector0 = format!("{:02x}", vector[0]);
    let vector1 = format!("{:02x}", vector[1]);
    let vector_combined = vector0 + &vector1;
    let number = u16::from_str_radix(&vector_combined, 16).unwrap();
    
  • There's no need to create two strings at all, one will do:

    let vector_combined = format!("{:02x}{:02x}", vector[0], vector[1]);
    let number = u16::from_str_radix(&vector_combined, 16).unwrap();
    

Of course, this still isn't the right solution, but it's better.

like image 180
Shepmaster Avatar answered Oct 05 '22 11:10

Shepmaster


You can multiply the first element to move it to the higher byte, then add the second element. It just needs extra casting:

let a: u8 = 1;
let b: u8 = 2;
let c: u16 = (a as u16 * 256) + b as u16;
println!("c: {}", c); // c: 258
like image 28
viraptor Avatar answered Oct 05 '22 11:10

viraptor