Base64 string ↔ float array I need to convert f32 arrays with a fix length to base64 representation and back.

My current code looks like this. It works, but it feels way too complicated.

How can I improve it?

main.rs:

extern crate base64;

fn read_float_vec_from_byte_vec(v: Vec&lt;u8&gt;) -&gt; Result&lt;Vec&lt;f32&gt;, String&gt; {
    match v.len() % 4 {
        0 =&gt; {
            use std::ptr;
            let p = v.as_ptr();
            let dest_len = v.len() / 4;
            let mut result = Vec::&lt;f32&gt;::with_capacity(dest_len);
            for i in 0..dest_len as isize {
                unsafe {
                    let f_ptr = p.offset(4 * i) as *const f32;
                    result.push(ptr::read::&lt;f32&gt;(f_ptr));
                }
            }
            Ok(result)
        }
        _ =&gt; Err("Number of bytes not divisible by 4.".to_string())
    }
}

fn float_vec_as_byte_vec(values: &amp;Vec&lt;f32&gt;) -&gt; Vec&lt;u8&gt; {
    let dest_len = values.len() * 4;
    let mut result = Vec::&lt;u8&gt;::with_capacity(dest_len);
    result.resize(dest_len, 0);
    for (i, x) in values.iter().enumerate() {
        let bytes: [u8; 4] = unsafe { std::mem::transmute(*x) };
        result[i*4..i*4+4].copy_from_slice(&amp;bytes[..]);
    }
    result
}

fn show_base64_decode_error(e: base64::DecodeError) -&gt; String {
    use std::error::Error;
    format!("{:?}", e.description())
}

fn base64_to_f32_16_array(base64_str: &amp;str) -&gt; Result&lt;[f32; 16], String&gt;
{
    base64::decode(base64_str
        ).map_err(show_base64_decode_error
        ).and_then(read_float_vec_from_byte_vec
        ).map(
        |float_vec| {
            let mut result: [f32; 16] = [0.0; 16];
            result.copy_from_slice(&amp;float_vec[..]);
            result
        }
    )
}

fn f32_16_array_to_base64(vals: &amp;[f32; 16]) -&gt; String
{
    base64::encode(&amp;float_vec_as_byte_vec(&amp;vals.to_vec()))
}

#[cfg(test)]
mod tests {
    static VALUES: [f32; 16] = [-0.35, -0.97, 0.05, 0.49, 1.35, 1.08, -0.71, 0.27, -0.44, 0.22, -1.34, -1.17, 0.68, 0.02, -0.52, 0.83];
    static BASE64_STR: &amp;str = "MzOzvuxReL/NzEw9SOH6Ps3MrD9xPYo/j8I1v3E9ij6uR+G+rkdhPh+Fq7+PwpW/exQuPwrXozy4HgW/4XpUPw==";
    #[test]
    fn base64_to_f32_16_array_test() {
        assert_eq!(super::base64_to_f32_16_array(&amp;BASE64_STR), Ok(VALUES));
        assert_eq!(
            super::base64_to_f32_16_array("foo"),
            Err("Number of bytes not divisible by 4.".to_string()));
    }
    #[test]
    fn f32_16_array_to_base64_test() {
        assert_eq!(super::f32_16_array_to_base64(&amp;VALUES), BASE64_STR);
    }
}


cargo.toml (for completeness):

[package]
name = "base64_f32_test"
version = "0.1.0"

[dependencies]
base64 = "0.9.0"
You should use iterators: when you use a line like this for i in 0..dest_len as isize, the compiler cannot optimize when going through the slice.

A better example:

fn read_float_vec_from_byte_vec(v: Vec&lt;u8&gt;) -&gt; Result&lt;Vec&lt;f32&gt;, &amp;'static str&gt; {
    #[inline(always)]
    fn float_from_u8_slice(s: &amp;[u8]) -&gt; f32 {
        let array = [s[0], s[1], s[2], s[3]];
        unsafe { std::mem::transmute(array) }
    }

    match v.len() % 4 {
        0 =&gt; Ok(v.chunks(4).map(float_from_u8_slice).collect()),
        _ =&gt; Err("Number not divisible by 4"),
    }
}


With the iteration over v.chunks(4).map(float_from_u8_slice).collect(), the code is smaller and more readable. Generally speaking, iterators are always better than indexing because indexing operation performs bounds checks.

Unfortunately, you cannot do the same with float_vec_as_byte_vec because (for now) arrays in Rust are not a first-class citizen. Due to the fact that genericity on integers does not exist, you cannot consume an array into an iterator. If it did, we could write something like:

// This code does not work currently
fn float_vec_as_byte_vec(s: &amp;[f32]) -&gt; Vec&lt;u8&gt; {
    s.iter().flat_map(|&amp;f| unsafe { transmute::&lt;f32, [u8; 4]&gt;(f)).collect()
}


In this code, I accept the type &amp;[f32], instead of &amp;Vec&lt;f32&gt;. Slices are a view of memory, and that is what we want. &amp;Vec&lt;f32&gt; does not really make sense, semantically.

In the first function I rewrote, I used &amp;'static str and not String because we do not need an allocated string since it is literal.