Why is there a difference between these lines of code


#1

I was trying to figure out a way to create Fibonacci sequences. I came across some topics on the net but these had long solutions that involved structs with iterators on impl …etc
Since I am not that familiar with rust’s features, I thought I first tried doing something I was more comfortable first and try and migrate to structs and impl…etc as I learn rust. This is when I came across these little peculiarity.
This first code creates a Fibonacci sequence up to the 94th element.

fn main() {
    let mut my_vec = (0..50).collect::<Vec<u64>>();
        let mut fib:Vec<u64> = vec![0;50];
        for x in 0..fib.len() {
            if x == 1 {
                fib[x] = 1u64;
            } else if x > 1 {
                fib[x] = fib[x-1] + fib[x-2];
            }
        }
        println!("{:?}", fib);
}

This next one creates only till the 47th

fn fibonacci_list(length: u64) {

    let mut my_vec = (0..length).collect::<Vec<u64>>();
    let mut fib = vec!(0;length as usize);
    for x in 0..fib.len() {
        if x == 1 {
            fib[x] = 1;
        } else if x > 1 {
            fib[x] = fib[x-1] + fib[x-2];
        }
    }
    println!("{:?}", fib);
}

fn main() {
    fibonacci_list(47);
}

The error at the 48th shows as an attempt to add with overflow.
I don’t really understand where the overflow is.
Any help here would be appreciated.

Thank you in advance


#2

https://doc.rust-lang.org/stable/reference/tokens.html#integer-literals

The short of it is that type inference tried to determine the precise integer type from context/usage (when not explicitly specified). The default is i32, which is what happens in your 2nd example. In the first example, you explicitly say it’s a vec of u64s.


#3

Instead of using all the intermediate Vec's and collect() calls, it might be easier to use prev and current variables and a for loop which adds the latest item to a Vec.

use std::mem::swap;

fn fib(n: usize) -> Vec<usize> {
    let mut numbers = Vec::new();
    let mut prev = 1;
    let mut current = 1;

    for _ in 0..n {
        numbers.push(prev);
        swap(&mut prev, &mut current);
        current += prev;
    }

    numbers
}

You’ll usually want to create an Iterator so you don’t need to store sequence in memory, but Fibonacci is so simple you’d end up writing more “boilerplate” (i.e. the struct and iterator impls you were talking about earlier) than actual code.


Here’s a real-life example of where iterators make your life easier and increase readability. In the snippet gearbox_combinations() returns an iterator which gives me every permutation of 4 gears with tooth numbers in the range 18..300 and I’m applying various filters to get just the combination of gears which satisfy some constraints. If I were to store each combination in a Vec up front it’d require something like 1000x the amount of RAM on my computer.

    let candidates: Vec<Gearbox> = gearbox_combinations()
        .filter(shafts_are_parallel)
        .filter(is_speed_reducer)
        .filter(gearbox_within_bounds)
        .filter(within_practical_geartrain_limits)
        .collect();

If you were to do this without iterators:

let mut candidates = Vec::new();

for n1 in 18..300 {
    for n2 in 18..300 {
        for n3 in 18..300 {
            for n4 in 18..300 {
                let gb = (n1, n2, n3, n4);

                if shafts_are_parallel(gb) {
                    if is_speed_reducer(gb) {
                        if gearbox_within_bounds(gb) {
                            if within_practical_geartrain_limits(gb) {
                                candidates.push(gb);
                            }
                        }
                    }
                }
            }
        }
    }
}

The awesome thing is that the first snippet actually compiles down to something like the second snippet once the compiler does optimisations like inlining and constant folding. Meaning that filtering through all 6,324,066,576 possible gearbox combinations only took 1.6 seconds on my laptop.

EDIT: Oops, sorry for the massive response. I may have gotten a little carried away there…


#4

Hi vitalyd. Thanks for the reply. Indeed I had tried using Vec as a type for the fib vector, but when I do this, the IDE gives a warning saying that there is a mismatch type. I.e.
If I rewrite
let mut fib = vec!(0;length as usize);
as
let mut fib:Vec<u64> = vec!(0; length as usize);
the IDE (which is IntelliJ, by the way) says it expected a Vec but found a Vec instead.
The compiler does not actually give out any errors, so, I guess I kinda panicked.
Thanks


#5

Yeah, sorry about that, out type inference is not perfect at the moment, and can give false positive warnings :frowning:

We’ve disables this inspection in the latest release.


#6

Hi Michael-F-Bryan
Thank you very much for the response. I really liked the fact that it was rich in information. This is what I’ve been looking in order to get better in Rust. Actual tips to research into.
The main difficulties I’ve had were related to some of the things I would like to do in the future with rust. I work with computational chemistry and we usually work a lot with vectors. So, efficient ways to iterate over them without increased memory consumption is a must.
I’ve struggled a bit for the past few days due to Rust’s ownership restraints while modifying vectors while iterating them. But I guess that once I master the use of iterators and so on, I’ll be able to use all the magic of rust.
In the mean while, any advice is more than welcome.
Thank you


#7

I see @matklad has cleared up the IDE aspect. I just wanted to note that you can write the above as let mut fib = vec![0u64; length as usize] (note the 0u64) - that specifies the type in the integer literal and is shorter; it’s also the more conventional way to provide the type for cases like this.


#8

Wow…kk…indeed. I’ve just updated and the warnings are not there anymore…
Thanks a lot.