Why this mismatch error? and how to write simpler?

This code:

  let mut small_cousins = vec![];
  if end_num < 49 {
    for c_hi in [11, 17, 23, 41, 47 ].iter().enumerate() {
      if start_num <= c_hi && c_hi <= end_num { small_cousins.push(c_hi); }
    if small_cousins.is_empty() {
      cousinscnt = 0; last_cousin = 0; 
     } 
    else {
      cousinscnt = small_cousins.len(); last_cousin = small_cousins.iter().max(); };
    }
  }

produces the following errors, and I've tried everything, and finally have to ask why.
And I'm sure this snippet can be written simpler.
Help will be appreciated.

➜  cousinprimes_ssoz RUSTFLAGS="-C opt-level=3 -C debuginfo=0 -C target-cpu=native" cargo build --release
   Compiling cousinprimes_ssoz v1.0.0 (/home/jzakiya/rust-projects/cousinprimes_ssoz)
error[E0308]: mismatched types
   --> src/main.rs:317:23
    |
317 |       if start_num <= c_hi && c_hi <= end_num { small_cousins.push(c_hi); }
    |                       ^^^^ expected `usize`, found tuple
    |
    = note: expected type `usize`
              found tuple `(usize, &{integer})`

error[E0308]: mismatched types
   --> src/main.rs:317:39
    |
317 |       if start_num <= c_hi && c_hi <= end_num { small_cousins.push(c_hi); }
    |                                       ^^^^^^^ expected tuple, found `usize`
    |
    = note: expected tuple `(usize, &{integer})`
                found type `usize`

error[E0308]: mismatched types
   --> src/main.rs:321:55
    |
321 |       cousinscnt = small_cousins.len(); last_cousin = small_cousins.iter().max(); };
    |                                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `usize`, found enum `Option`
    |
    = note: expected type `usize`
               found enum `Option<&(usize, &{integer})>`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `cousinprimes_ssoz` due to 3 previous errors
➜  cousinprimes_ssoz 

There's a lot of missing context, so it's hard to say for sure, but here's what I came up with after just adding types until it compiled.

fn f(end_num: u128, start_num: u128, mut cousinscnt: usize, mut last_cousin: u128, c_hi: u128) {
    let mut small_cousins = vec![];
    if end_num < 49 {
        for c_hi in [11, 17, 23, 41, 47].into_iter() {
            if start_num <= c_hi && c_hi <= end_num {
                small_cousins.push(c_hi);
            }
            if small_cousins.is_empty() {
                cousinscnt = 0;
                last_cousin = 0;
            } else {
                cousinscnt = small_cousins.len();
                last_cousin = small_cousins.iter().copied().max().unwrap();
            }
        }
    }
}

The first main change was

-    for c_hi in [11, 17, 23, 41, 47 ].iter().enumerate() {
+    for c_hi in [11, 17, 23, 41, 47].into_iter() {

iter() iterates over &{integer}s and enumerate() turns that into (usize, &{integer}) (where {integer} is the type of the numbers; inferred to be u128 in my version). You didn't seem to be using the index returned by the enumeration, so I got rid of that. into_iter() consumes the array and returns {integer} instead of references. An alternative if you don't want to consume the array is .iter().copied().

The second main change was

- last_cousin = small_cousins.iter().max(); };
+ last_cousin = small_cousins.iter().copied().max().unwrap();

Again, we want to iterate over integers, not references, so I added .copied(). The output of max() is an option that will be None when the small_cousins is empty. We know that's not the case here, so I just used unwrap(). An alternative might be something like .unwrap_or(0), depending on the use case (similar to below).


That was the minimal set of changes I made. However, I note that your small_cousins vector, as presented here, is always sorted. Instead of iterating to find the max, you know that the max is the last element. If that's always true, you could make this change:

-            if small_cousins.is_empty() {
-                cousinscnt = 0;
-                last_cousin = 0;
-            } else {
-                cousinscnt = small_cousins.len();
-                last_cousin = small_cousins.iter().copied().max().unwrap();
-            }
+            cousinscnt = small_cousins.len();
+            last_cousin = small_cousins.last().copied().unwrap_or(0);

And there might be something you could do algorithmically around your "is c_hi in range" check too, but it's hard to be certain without more context.

1 Like

Thanks again for your quick, and accurate response, and catching the array was already sorted (I slept that one).

Here's the final code.

  let mut small_cousins = vec![];
  if end_num < 49 {
    for c_hi in [11, 17, 23, 41, 47 ].into_iter() {
      if start_num <= c_hi - 4 && c_hi <= end_num { small_cousins.push(c_hi); }
      if small_cousins.is_empty() { cousinscnt = 0; last_cousin = 0; } 
      else { cousinscnt = small_cousins.len(); last_cousin = small_cousins.last().copied().unwrap(); };
    }
  }

I don't use Rust enough to know|remember all these nuances (ref vs value, etc), and even sometimes when I see it explained in writing it just bounces off my brain.

Here's the Crystal snippet I was trying to translate. The whole Rust code works now.

  if end_num < 49
    small_cousins = [11, 17, 23, 41, 47 ].select { |c_hi| start_num <= c_hi - 4 && c_hi <= end_num }
    cousinscnt, last_cousin = small_cousins.empty? ? {0,0} : {small_cousins.size, small_cousins.last}
  end

This:

      if small_cousins.is_empty() { cousinscnt = 0; last_cousin = 0; } 
      else { cousinscnt = small_cousins.len(); last_cousin = small_cousins.last().copied().unwrap(); }

Can be rewritten as this:

      // Changed:     vvvvvvvvvvv
      if small_cousins.len() == 0 { cousinscnt = 0; last_cousin = 0; } 
      else { cousinscnt = small_cousins.len(); last_cousin = small_cousins.last().copied().unwrap(); }

Can be rewritten as this:

      cousinscnt = small_cousins.len(); // <-- taken from both branches
      if small_cousins.len() == 0 { last_cousin = 0; } 
      else { last_cousin = small_cousins.last().copied().unwrap(); }

Can be rewritten as this:

      cousinscnt = small_cousins.len();
      last_cousin =
        if small_cousins.len() == 0 { 0 } 
        else { small_cousins.last().copied().unwrap() };

Can be rewritten as this:

      cousinscnt = small_cousins.len();
      last_cousin = match small_cousins.last().copied() {
          None => 0, // small_cousins is empty (no last element)
          Some(max) => max, // small_cousins is not empty
      };

Can be rewritten as this:

      cousinscnt = small_cousins.len();
      last_cousin = small_cousins.last().copied().unwrap_or(0);

There's a good chance it all optimizes to the same thing, but I still find this form more elegant.

(Editted to fix copy-paste of match in last code block.)

1 Like

Actually, wait. Here's a more direct translation of that Crystal code.

    let small_cousins: Vec<_>;
    let (cousinscnt, last_cousin);
    if end_num < 49 {
        small_cousins = [11, 17, 23, 41, 47]
            .into_iter()
            .filter(|&c_hi| start_num <= c_hi && c_hi <= end_num)
            .collect();
        
        cousinscnt = small_cousins.len();
        last_cousin = small_cousins.last().copied().unwrap_or(0);
    }

(I thought you needed cousinscnt and last_cousin in the loop for whatever reason, but you don't. If you keep the for loop you can put those assignments after it.)

Edit: Or even

        small_cousins = [11, 17, 23, 41, 47]
            .into_iter()
            .skip_while(|&c_hi| c_hi < start_num)
            .take_while(|&c_hi| c_hi <= end_num)
            .collect();

Thanks for the alternative snippets.

Can you explain this.
When I run this:

  let small_cousins: Vec<_>;
  if end_num < 49 {
    small_cousins = [11, 17, 23, 41, 47].into_iter()
      .filter( |c_hi| start_num <= c_hi - 4 && c_hi <= end_num )
      .collect();
    cousinscnt  = small_cousins.len(); 
    last_cousin = small_cousins.last().copied().unwrap_or(0);
  }

The compiler (1.57) gives this error:

➜  cousinprimes_ssoz RUSTFLAGS="-C opt-level=3 -C debuginfo=0 -C target-cpu=native" cargo build --release
   Compiling cousinprimes_ssoz v1.0.0 (/home/jzakiya/rust-projects/cousinprimes_ssoz)
error[E0308]: mismatched types
   --> src/main.rs:316:56
    |
316 |       .filter( |c_hi| start_num <= c_hi - 4 && c_hi <= end_num )
    |                                                        ^^^^^^^
    |                                                        |
    |                                                        expected `&usize`, found `usize`
    |                                                        help: consider borrowing here: `&end_num`

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

and when I do the &end_num it compiles.
But the original code works fine.
Why?

ADDTIONAL

I can shorten the Crystal snippet like this, to mimic your implementation.

if end_num < 49
  small_cousins = [11, 17, 23, 41, 47 ].select { |c_hi| start_num <= c_hi - 4 && c_hi <= end_num }
  cousinscnt, last_cousin = small_cousins.size, small_cousins.last? || 0
end

I like this the best, as it and the Crystal version are the shortest, and do the same thing, and it compiles with no problems.

  let mut small_cousins = vec![];
  if end_num < 49 {
    for c_hi in [11, 17, 23, 41, 47 ].into_iter() {
      if start_num <= c_hi - 4 && c_hi <= end_num { small_cousins.push(c_hi); }
    }
    cousinscnt  = small_cousins.len();
    last_cousin = small_cousins.last().copied().unwrap_or(0);
  }

For the error itself, see this recent thread. As for why various things work around it, with yours you turn an {integer} into a &{integer} to make the comparison work:

//    Comparison works on &int <= &int   vvvvvvvvvvvvvvvv
.filter( |c_hi| start_num <= c_hi - 4 && c_hi <= &end_num )
//  Sub works on &int - int  ^^^^^^^^  (produces int)

Where as in my version, the pattern binds c_hi to the value behind the reference:

//        vvvvv closure gets a &{integer}, so c_hi is an {integer}
.filter( |&c_hi| start_num <= c_hi - 4 && c_hi <= end_num )
//    Sub works on int - int  ^^^^^^^^    ^^^^^^^^^^^^^^^
//       Comparison works on int <= int   |||||||||||||||

One nice thing about this approach is that if you had more than one {integer}/&{integer} mismatch in the closure, they would all be resolved.

You may be familiar with patterns in match blocks, but they're also used in function parameters (like in this closure), let statements, and more. Here's a more in-depth look at the duality between patterns and the expressions they apply to.

If you just want the count and the last item, there's no need to allocate an intermediate vector:

let (count, last) = [11, 17, 23, 41, 47].into_iter()
    .filter(|c_hi| start_num <= c_hi - 4 && c_hi <= end_num)
    .enumerate()
    .map(|(i, v)| (i + 1, v))
    .last()
    .unwrap_or((0, 0));

Yes that works in my codebase (with these changes) and produces correct results.

if end_num < 49 {
  let (_cousincnt, _last_cousin) = [11, 17, 23, 41, 47].into_iter()
     .filter(|c_hi| start_num <= c_hi - 4 && c_hi <= &end_num)
     .enumerate().map(|(i, v)| (i + 1, v))
     .last().unwrap_or((0, 0));
}

The compiler (1.57) squawked with a warning doing this: let (cousincnt, last_cousin)
because they were already defined, and suggested putting underscores if that's what I meant.
And again, without the: &end_num it wouldn't compile.

This is something I would never have envisioned writing because I'm not that an advanced Rust programmer. But I do understand it from looking at it. I'm learning something everyday. :slightly_smiling_face:

Thanks again to all.

That warning is pointing out a bug -- you computed but didn't use these values. Looking at your original code, you will want to assign them back to the mutable arguments that were passed into the method, no?

I originally wrote it like this:

if end_num < 49 {
  let (count, last) = [11, 17, 23, 41, 47].into_iter()
    .filter(|c_hi| start_num <= c_hi - 4 && c_hi <= &end_num)
    .enumerate().map(|(i, v)| (i + 1, v))
    .last().unwrap_or((0, 0));
  cousinscnt = count; last_cousin = last;
}

But I wanted to see if it worked by directly setting cousinscnt|last_cousin, which it does.

This snippet sets cousinscnt|last_cousin for this specific edge case. Otherwise they're set for inputs that are normally (much) larger. I account for these cases just to be accurate for handling these small inputs.

So I was able to eliminate that last line, just to see if I could shorten the code.

From a total program functional view, it doesn't really matter what the code is to perform this check (correctly). But from an educational perspective, it's good to see alternatives I can use later.

EDIT:
Well, for some small input cases removing the last line produces incorrect results, so it's back to the original form with explicit assignment of those variables. Now, I have to figure out why?

My understanding based on the short snippet of code you started off with is that cousinscnt and last_cousin were defined before the if end_num < 49 line and initialized with some default values. And your code should update the outer variables only if the if statement is true.

But note that

if end_num < 49 {
  let (cousincnt, last_cousin) = ...; // `let` creates new local bindings
}

creates new variables local to the if block, shadowing the outer variables since they have the same name (that's what let bindings do -- they declare new bindings/variables). That's the bug with removing that last line. You're no longer updating the outer variables.

Unfortunately, I'm not aware of any way to assign to multiple variables in a single statement in rust like there is in Crystal (and ruby, python, and others). I know folks had asked about this before and I too wish it existed.

...

WAITAMINUTE! While I was living under a rock, this feature was RFCed, implemented, and is scheduled to be stable in rust 1.59 (should come out mid-to-late Feb). Now that's a nice present!

So with that, you can write the following in rust nightly (not stable):

#![feature(destructuring_assignment)] // top of the file

...

if end_num < 49 {
  (cousincnt, last_cousin) = ...;  // <-- no `let`
}

and your code will work. Lookin' forward to 1.59.

Now that is good news.

Maybe Santa checks Rust's forum, to see whose been naughty or nice! :grinning_face_with_smiling_eyes:

Confirmed the 1.59 nightly (as of T 2021/12/28) works as stated.

Using rustup did following to load on my Linux i7 based system.

➜  ~ rustup update nightly
info: syncing channel updates for 'nightly-x86_64-unknown-linux-gnu'
info: latest update on 2021-12-27, rust version 1.59.0-nightly (f8abed9ed 2021-12-26)
info: downloading component 'cargo'
  6.2 MiB /   6.2 MiB (100 %)   5.5 MiB/s in  1s ETA:  0s
info: downloading component 'clippy'
info: downloading component 'rust-docs'
 19.2 MiB /  19.2 MiB (100 %)   5.1 MiB/s in  3s ETA:  0s
info: downloading component 'rust-std'
 25.7 MiB /  25.7 MiB (100 %)   3.8 MiB/s in  6s ETA:  0s
info: downloading component 'rustc'
 54.5 MiB /  54.5 MiB (100 %)   4.5 MiB/s in 13s ETA:  0s
info: downloading component 'rustfmt'
info: installing component 'cargo'
info: installing component 'clippy'
info: installing component 'rust-docs'
 19.2 MiB /  19.2 MiB (100 %)   9.0 MiB/s in  2s ETA:  0s
info: installing component 'rust-std'
 25.7 MiB /  25.7 MiB (100 %)  12.0 MiB/s in  2s ETA:  0s
info: installing component 'rustc'
 54.5 MiB /  54.5 MiB (100 %)  12.8 MiB/s in  4s ETA:  0s
info: installing component 'rustfmt'

  nightly-x86_64-unknown-linux-gnu installed - rustc 1.59.0-nightly (f8abed9ed 2021-12-26)

info: checking for self-updates

Then changed to default, and ran rustup show to confirm.

➜  ~ rustup default nightly
info: using existing install for 'nightly-x86_64-unknown-linux-gnu'
info: default toolchain set to 'nightly-x86_64-unknown-linux-gnu'

  nightly-x86_64-unknown-linux-gnu unchanged - rustc 1.59.0-nightly (f8abed9ed 2021-12-26)

➜  ~ rustup show
Default host: x86_64-unknown-linux-gnu
rustup home:  /home/jzakiya/.rustup

installed toolchains
--------------------

stable-x86_64-unknown-linux-gnu
nightly-x86_64-unknown-linux-gnu (default)
1.40-x86_64-unknown-linux-gnu

active toolchain
----------------

nightly-x86_64-unknown-linux-gnu (default)
rustc 1.59.0-nightly (f8abed9ed 2021-12-26)

Now this code compiles (no warnings) and produces correct results.

if end_num < 49 {
  (cousinscnt, last_cousin) = [11, 17, 23, 41, 47].into_iter() 
    .filter(|c_hi| start_num <= c_hi - 4 && c_hi <= &end_num)
    .enumerate().map(|(i, v)| (i + 1, v))
    .last().unwrap_or((0, 0));
}

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.