Why do I need to dereference when comparing but not when adding

New rust user here, figuring things out, would appreciate some help, thanks in advance!

Brief context: I'm working on Day 3 - Advent of Code 2016 which requires reading numbers from a file. My code does this:

let contents = fs::read_to_string("3.txt").expect("File not found");

let mut valid = 0;
for line in contents.split("\n") {
  if let [a, b, c] = &line
    .trim()
    .split_whitespace()
    .map(|x| x.parse::<i32>().expect("Unparsable number"))
    .collect::<Vec<i32>>()[..]
  {
    if a + b > *c && b + c > *a && c + a > *b {
      valid += 1;
    }
  }
}

3.txt looks like

  785  516  744
  272  511  358
  801  791  693
  572  150   74

What I'm confused by is the line:

if a + b > *c && b + c > *a && c + a > *b {

If I add or remove prefix * on the addends, it doesn't seem to matter, but if I leave out the * before the right hand side of the >, I get:

calebegg@penguin:~/adventofcode/2016$ cargo run --bin 3-1
   Compiling adventofcode-2016 v0.1.0 (/home/calebegg/adventofcode/2016)
error[E0308]: mismatched types
  --> src/bin/3-1.rs:14:18
   |
14 |       if a + b > c && b + c > *a && c + a > *b {
   |                  ^ expected `i32`, found `&i32`
   |
help: consider dereferencing the borrow
   |
14 |       if a + b > *c && b + c > *a && c + a > *b {
   |                  +

For more information about this error, try `rustc --explain E0308`.
error: could not compile `adventofcode-2016` due to previous error

Can someone explain or link to some relevant documentation? Thanks! Also, if there's anything un-idiomatic in what I'm doing I'd love to hear it. I was a little surprised to need to collect to a vec to destructure.

1 Like

Comparison operators in Rust often only work when both sides have the same type. (At least for numbers and references to numbers like i32 or &i32 that's the case.) Adding two numbers always produces an owned value. No matter if you

  • add &i32 to &i32 (a + b)
  • or i32 to i32 (*a + *b)
  • or even mixed should work (I think?)

the result always will be i32 on the left hand side of the >, so an i32 is necessary in the right hand side, too.

The Add trait is implemented for integer + reference-to-integer too, not just integer + integer: https://doc.rust-lang.org/std/primitive.i32.html#impl-Add<%26'_%20i32>-1

This is pretty ad-hoc rather than idiomatic (Rust doesn't have built-in collection into arrays yet), but you can gather your three i32s without allocating:

// Panics on unparsable numbers
// Returns None for 0, 1, 2, or more than 3 numbers
fn three_i32s(line: &str) -> Option<[i32; 3]> {
    let mut iter = line
        .split_whitespace()
        .map(|x| x.parse::<i32>().expect("Unparsable number"));

    let a = iter.next()?;
    let b = iter.next()?;
    let c = iter.next()?;
    
    match iter.next() {
        None => Some([a, b, c]),
        _ => None,
    }
}

There's no need for trim due to the split_whitespace. Then your loop becomes:

    for line in contents.split("\n")
        if let Some([a, b, c]) = three_i32s(line) {
            if a + b > c && b + c > a && c + a > b {
                valid += 1;
            }
        }
    }

But the pattern for x in iter { if let Some(y) = expression(x) { /*...*/ } } can be more succinctly written with flat_map:

    for [a, b, c] in contents.split("\n").flat_map(|line| three_i32s(line)) {
        if a + b > c && b + c > a && c + a > b {
            valid += 1;
        }
    }

And since all we're doing in the flat_map closure is calling a function with the same argument as the closure, this can in turn be shortened to:

    for [a, b, c] in contents.split("\n").flat_map(three_i32s) {
        if a + b > c && b + c > a && c + a > b {
            valid += 1;
        }
    }

Playground.

3 Likes

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.