1

I'm new to rust I'm finished reading chapter 1 of the purple crab book and I'm stuck.
I'm using this crate for sha1 https://docs.rs/sha-1/0.9.1/sha1/

I want to return the output of m.finalize(); How do I achieve this in rust?

in python I'd do something like this

def gen_chain():
    chain_redux = "some_Str"
    hash = None 
    hasher = Sha1()
    for i in range(0,iterations):
        hasher.update(chain_redux)
        hash = hasher.finalize() 
        hasher.reset()
        chain_redux = redux(hash)
        print(chain_redux)
    return hash

Heres what I have in rust

fn calc_chain(name_table:&Vec<String> ,iterations:i32) -> GenericArray<u8, <sha1::Sha1 as Digest>::OutputSize>{

    let mut m = sha1::Sha1::new();
    let mut chain_redux = &name_table[0];
    let mut hash;
    // The above doesnt fly in rust. and i can't figure out how to make the arr! macro return a 
    // GenericArray<u8, <sha1::Sha1 as Digest>::OutputSize> I also don't know if I should. 
    for i in 0..iterations {
        println!("Curr string: {}",chain_redux);
        m.update(chain_redux);
        hash = m.finalize(); // let hash = m.finalize() here keeps me from returning it because rust 
                             // deletes it after the for loop scope
        m.reset();
        chain_redux = redux(&hash, name_table);
        println!("Redux Output: {}",chain_redux);
    };
    hash 

}

Any rust tips/pointers would be appreciated here also.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
DNS_Jeezus
  • 289
  • 4
  • 17
  • Related: [How can I iteratively call Sha256::digest, passing the previous result to each subsequent call?](https://stackoverflow.com/q/62678226) – Sven Marnach Oct 26 '20 at 12:54

3 Answers3

2

I think the problem here is that in the Python code, you have a special value you return if the loop executes zero times (because iterations = 0) - you return None. In Python, any variable can be None so this is fine.

In Rust, there is no None value by default for variables - a variable of type GenericArray can't be None, so you need to be more explicit and use a type which can be None, such as an Option (which is a type which can be None or Some). Something like:

fn calc_chain(name_table:&Vec<String> ,iterations:i32) -> Option<GenericArray<u8, <sha1::Sha1 as Digest>::OutputSize>> {
    let mut m = sha1::Sha1::new();
    let mut chain_redux = &name_table[0];
    let mut maybe_hash = None;
    for i in 0..iterations {
        println!("Curr string: {}",chain_redux);
        m.update(chain_redux);
        let this_iteration_hash = m.finalize()
        m.reset();
        chain_redux = redux(&this_iteration_hash, name_table);
        println!("Redux Output: {}",chain_redux);
        maybe_hash = Some(this_iteration_hash)
    };
    maybe_hash 
}

If you have a default value you'd like to return instead of an Option you could change the line which just reads maybe_hash to instead use unwrap_or or unwrap_or_else:

maybe_hash.unwrap_or_else(make_default_array)

Alternatively, if you know that iterations is always greater than 0, you could return a GenericArray by changing the maybe_hash line to:

maybe_hash.unwrap()

which is asserting that maybe_hash is not None, but if someone does call your function with iterations = 0, the program will panic.

Daniel Wagner-Hall
  • 2,446
  • 1
  • 20
  • 18
1

One of Rustacean principles is to use iterators as much as possible as they're frequently faster than plain for loops and the standard library has quite many fancy functions to solve anything with iterators.

Unfortunately the approach with iterators is not ideal here. Firstly, because it doesn't look fancy and we have to use ugly .last().unwrap() to get the last hash. Secondly, the map() iterator there has side-effects (it changes m and chain_redux) which is not so idiomatic for iterators, yet doing it idiomatically would require a few more lines for fold() iterator

fn calc_chain(name_table: &Vec<String>, iterations:i32) -> GenericArray<u8, <sha1::Sha1 as Digest>::OutputSize> {
    let mut m = sha1::Sha1::new();
    let mut chain_redux = &name_table[0];
    (0..iterations)
        .map(|_| { 
            m.update(chain_redux);
            let hash = m.finalize_reset()
            chain_redux = redux(&hash, name_table);
            hash 
        })
        .last()
        .unwrap()
}

If you're yet unfamiliar with iterators you can read it as follows:

  1. 0..iterations is the same iterator you used in your for loop, it will give you numbers 0, 1, 2, .. up to iterations-1 if you ask it to do so
  2. .map(|_| { ... }) is an iterator which when you ask it for a value, it will take a value from previous iterator (i.e. numbers from 0..iterations) and will return another value based on given function. There, for each iteration a corresponding hash will be returned
  3. .last() is the function that finally starts to ask previous iterator to give values, and as soon as the previous iterator has no more values to give on demand it will return the last value (i.e. the last hash)
  4. .unwrap() is needed because .last() actually returns an Option, but we believe that there was atleast one computed hash, so we just unwrap it
Alexey S. Larionov
  • 6,555
  • 1
  • 18
  • 37
-1

You can do it this way:

// Use NonZeroU32 because we always run one iteration
fn calc_chain(name_table:&Vec<String> ,iterations:NonZeroU32) -> GenericArray<u8, <sha1::Sha1 as Digest>::OutputSize>{
    let mut m = sha1::Sha1::new();
    let mut chain_redux = &name_table[0];
    let mut hash = {
        // Run first iteration
        println!("Curr string: {}",chain_redux);
        m.update(chain_redux);
        let hash = m.finalize();
        m.reset();
        chain_redux = redux(&hash, name_table);
        println!("Redux Output: {}",chain_redux);
        hash // Scope returns first value
    };
    for i in 1..iterations.get() {
        println!("Curr string: {}",chain_redux);
        m.update(chain_redux);
        hash = m.finalize();
        m.reset();
        chain_redux = redux(&hash, name_table);
        println!("Redux Output: {}",chain_redux);
    };
    hash 
}