hooinator
hooinator

Reputation: 339

How to implement Ruby's unpack in Rust?

I'm struggling to figure out how to implement the following unpack('IIII') Ruby statement in Rust.

require 'digest'

md5_digest_unpacked = Digest::MD5.digest(someString + "\x00").unpack('IIII')

I have gotten as far as generating the md5 portion with the following. The digests are the same between Ruby and Rust.

let digest = md5::compute(format!("{}{}", &someString, "\x00"));

However, I'm the not sure how to implement unpack('IIII').

Upvotes: 0

Views: 127

Answers (1)

Ian S.
Ian S.

Reputation: 1971

As far as I know Rust does not have a drop-in replacement for unpack, but there are two ways to get equivalent behavior here.

The Safe Way

use std::mem;
use std::convert::TryInto;

let mut dest = [0u32; 4];
let mut iter = digest.0.chunks(mem::size_of::<u32>())
    .map(|chunk| u32::from_ne_bytes(chunk.try_into().unwrap()));
dest.fill_with(|| iter.next().unwrap());
let [a, b, c, d] = dest;

The upside is that it's safe, the downside is that there are a couple unwraps required but those are infallible given that digest.0 is [u8; 16], and should be optimized out.

The Unsafe Way

Since you're converting to the native endian, you can just transmute the digest:

let [a, b, c, d] = unsafe { std::mem::transmute::<_, [u32; 4]>(digest.0) };

This transmute is safe because [u32; 4] and [u8; 16] have the same size and are both POD. You can find safe wrappers for these kinds of conversions through the bytemuck crate if you're fine with adding another dependency.

Edit: with -C opt-level=3, both methods have the same generated assembly.

Upvotes: 2

Related Questions