Concatenating and slicing sequences - arrays/Vecs/slices?

I have a function that needs to do some manipulations on several sequences (of bytes, but the question applies to any type) involving slicing and concatenation. While I managed to produce a working version of what I want, it looks unwieldy and I have a feeling I'm doing something wrong with all these multiple .iter() and .chain().

In idiomatic Rust, what types would you expect such a function to take - array slices, Vecs, something else? What type would it be expected to return? What types should I use for temporary storage inside the function? Is there a clearer way to perform concatenations?

Basically the function finds a hash that matches certain conditions. It is a simplified example, so it may not make a lot of sense as is.

use blake2::{Blake2b, Blake2s, Digest};

fn to_fixed_be_bytes(x: usize) -> [u8; 4] {
    let data = x.to_be_bytes();
    let l = data.len();
    let s = if l > 4 { 4 } else { l };

    let mut res = [0u8; 4];
    for i in 0..s {
        res[i] = data[i + l - s];
    }
    res
}

fn find_hash(data: &[u8], label: &[u8]) -> Option<Vec<u8>> {

    let len_data = to_fixed_be_bytes(data.len());
    let len_label = to_fixed_be_bytes(label.len());

    // label_data = len_label + label + len_data + data
    let label_data: Vec<u8> = len_label.iter()
        .chain(label.iter())
        .chain(len_data.iter())
        .chain(data.iter()).cloned().collect();

    let key_size_bytes = 32;

    let mut i = 0u32;
    while i < <u32>::MAX {
        let ibytes = to_fixed_be_bytes(i as usize);

        // to_hash = label_data + ibytes
        let to_hash: Vec<u8> = label_data.iter().chain(&ibytes).cloned().collect();

        let mut hash_function = Blake2b::new();
        hash_function.update(to_hash);
        let hash_digest_full = hash_function.finalize();
        let hash_digest = &hash_digest_full[0..1+key_size_bytes];

        let sign = if hash_digest[0] & 1 == 0 { b"\x02" } else { b"\x03" };

        // point = sign + hash_digest[1..end]
        let point: Vec<u8> = sign.iter().chain(hash_digest[1..hash_digest.len()].iter()).cloned().collect();

        if point[1] == 0xff {
            return Some(point);
        }

        i += 1
    }

    None
}

fn main() {
    let data = b"abcdefg";
    let label = b"sdasdasd";
    let p = find_hash(&data[..], &label[..]);
    println!("{:?}", p);
}

I would probably be using extend_from_slice a lot more. Before the loop I would define:

let mut to_hash = Vec::with_capacity(label_data.len() + 4);
to_hash.extend_from_slice(&label_data);

then in the loop I would do:

to_hash.truncate(label_data.len());
to_hash.extend_from_slice(&ibytes);

This also allows you to reuse the allocation.

Additionally I would rewrite to_fixed_be_bytes to

fn to_fixed_be_bytes(x: usize) -> [u8; 4] {
    (x as u32).to_be_bytes()
}

see full code

Thank you, I see. So using mutable state here is considered more idiomatic? It is hard for me to get used to. I agree with your argument about the speed/allocation, but this function will hardly be a performance bottleneck for me.

(x as u32).to_be_bytes()

Yes, this is indeed a much better version.

I don't think that, in this particular case, there is an agreement on what would be more idiomatic.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.