Matching types with unwrap_or()


#1

I am a very novice programmer and very new Rustacean. I’m working my way through Steve Donovan’s “A Gentle Introduction to Rust” (which I highly recommend for anyone who may struggle with “The Rust Programming Language” like I did) using Rust v1.24.

In the section on Optional Values, he covers the unwrap_or method for the Option type to retrieve values from a slice as a shortcut to using just unwrap(). He states:

The types must match up - get returns a reference. so you have to make up a &i32 with &-1. Finally, again use * to get the value as i32.
let last = *slice.get(5).unwrap_or(&-1);

However, in my code below, I have found that this method works both with and without the *.

fn main() {
    let foo = [10, 20, 30, 40];

    for i in 0..foo.len()+1 {
        println!("{} {}", i, foo.get(i).unwrap_or(&-1));

	// this also works:
	// println!("{} {}", i, *foo.get(i).unwrap_or(&-1));
    }
}

Can someone please help me understand why I do or do not need the * as well as the role the & plays in the unwrap_or method? My fear is that if I do not understand the concepts of these methods and borrowing, I am not going to get very far.

Thanks in advance!


#2

The distinction is better exemplified if you do this:

fn main() {
    let foo = [10, 20, 30, 40];

    for i in 0..foo.len() + 1 {
        takes_i32(*foo.get(i).unwrap_or(&-1));
    }
}

fn takes_i32(_: i32) {}

takes_i32 takes an i32, not a &i32. The reason println! works is because the Display trait is implemented for i32 and &i32 (or more generally, for any T that implements Display, so does &T automatically). println! also does not require values - it can work with references as well.

The * (dereference) copies out the i32 from the &i32 you get back from the slice; since i32 is a Copy type, this is perfectly fine.

For Clone types (which all Copy types are as well), there’s also Option::cloned() which gives you back a value, rather than reference. So you can do:

// type ascription only used for illustration - you don't actually need it
let x: i32 = foo.get(i).cloned().unwrap_or(-1);

#3

This really helped, @vitalyd! The code you supplied compiled fine but when I removed the dereference (*), I got the following error:

error[E0308]: mismatched types
 --> takesi32.rs:5:19
  |
5 |         takes_i32(foo.get(i).unwrap_or(&-1));
  |                   ^^^^^^^^^^^^^^^^^^^^^^^^^
  |                   |
  |                   expected i32, found &{integer}
  |                   help: consider dereferencing the borrow: `*foo.get(i).unwrap_or(&-1)`
  |
  = note: expected type `i32`
             found type `&{integer}`

error: aborting due to previous error

I did not know that the println! macro works with both i32 and &i32 so when I changed my code to the following below, I got a similar error as I assume the compiler is treating x as i32 but get and unwrap_or return a reference (&i32).

fn main() {
    let foo = [10, 20, 30, 40];

    let mut x = 0;

    for i in 0..foo.len()+1 {
        x = foo.get(i).unwrap_or(&-1);
        println!("{} {}", i, x);
    }
}

#4

Whenever you see {integer} in error messages, it just means type inference in the compiler hasn’t fully concluded which exact integer type you’re after.

To make the compiler spit out a very precise type in the error message, you can unambiguously tell it which types you want:

fn main() {
    // we’ll specify i32 here
    let foo = [10i32, 20, 30, 40];
    // and here
    let mut x = 0i32;

    for i in 0..foo.len()+1 {
        x = foo.get(i).unwrap_or(&-1);
        println!("{} {}", i, x);
    }
}

#5

As for this part, println! works on references - whatever value you give it, it essentially does an & on it. So if you give it &5i32, it will make it an &&5i32. Then some pattern matching comes into play (I’m glossing over this to not bog down in those details - we can discuss that separately if you’re interested) that strips it back down to &5i32 (you can try printing &&&&&&&&&&&&5i32 and it’ll also work :slight_smile: )

The reason println does this is to not move (aka consume) the value away from you. That’s not interesting for Copy types, but if you wanted to print say a String (non Copy), you’d want to be able to use it afterwards.


#6

I know that I am not the first one to say this but I really like how helpful the rust compiler error messages are:

error[E0308]: mismatched types
 --> src/main.rs:8:13
  |
8 |         x = foo.get(i).unwrap_or(&-1);
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^
  |             |
  |             expected i32, found &i32
  |             help: consider dereferencing the borrow: `*foo.get(i).unwrap_or(&-1)`
  |
  = note: expected type `i32`
             found type `&i32`

error: aborting due to previous error

Indeed it does!