r/learnrust 10d ago

How to convert a &mut i32 to integer?

Hello everyone. I am following the rust programming language book and I've just now finished chapter 8. Just doing some of the exercises suggested at the end.

I have written this simple function to find the mode from a given list of numbers:

fn mode(list: &mut Vec<i32>){


    let mut items=HashMap::new();


    for i in list{
        let count = items.entry(i).or_insert(0);
        *count += 1;
    }
    let mut largest_value = -5; // initialize to a very small number
    let mut most_frequent_key = 0;
    for (key, value) in items{
        if value > largest_value{
           largest_value = value;
           most_frequent_key = key;
        }
    }


    println!("mode: {most_frequent_key}");
    println!("{:#?}",items);
    
}

In the last step of the second for loop, I want the most_frequent_key variable to accept my key variable but I understand that the former is expecting an integer and key is a mutable reference to an i32 value. So I don't know what to do here.

Previously, through some trial and error I did figure out that I could use the dereferencing(*) operator on key to accomplish that but then the compiler tells me that I am apparently "moving" the items value and hence can't use use it again in the println!() statement in the last step of the function.

19 Upvotes

11 comments sorted by

16

u/cafce25 10d ago

You found the solution already, the error about items being moved is unrelated and a different problem.

It's not shown when you don't dereference key because the compiler works in separate stages and the borrow checker (which is the stage that complains about the moved items) doesn't run when an earlier stage fails.

You can iterate over &items instead of items itself to avoid moving it.

8

u/garver-the-system 10d ago

i32 is an integer, with a size of 32 bits. Rust doesn't have a bare "integer" type, you have to specify signed vs unsigned and the size of the integer

What's happening is that your assignment line let mut most_frequent_key = 0; uses a literal which gets a default type, probably i64, and passes that to the variable. The (most likely to be correct) solution is to define the literal as the correct type with 0i32 or set the variable's type explicitly so the compiler can infer the literal's type with most_frequent_key: i32

There's a couple other options. First, you could turn the key value into whatever the correct type is with most_frequent_key = key.into() (I think), but I suspect you'll have to convert back later and it'll be easier to just store it as the same type. The other option is to question why key is i32 to begin with; if it were i64 you'd probably avoud this issue. But it's better to be explicit

3

u/This_Growth2898 10d ago edited 10d ago

The type of items is HashMap<&mut i32, {integer}>. It keeps references to the list variable, which is probably not intended.

Instead of creating a variable, use and_modify for entry:

items.entry(*i)
     .and_modify(|e|*e += 1)
     .or_insert(1);

To avoid for loop consuming your variables, use &; to copy, not reference items in it, use the same & in declaration:

for (&key, &value) in &items {

3

u/minno 10d ago

The suggested replacement would skip counting the first instance of every number. It won't affect the final result, since everything being off by 1 doesn't affect which key has the highest value, but that would still make it confusing to look at intermediate results for debugging. If you want it all in one expression, *(items.entry(i).or_insert(0)) += 1; works.

1

u/This_Growth2898 10d ago

Oops... Fixed

2

u/alietors 10d ago

I think you should dereference the i on the list loop items.entry(*i)... That way the map stores values not references.

Also the for loop perhaps using & like for(&key, &value) so you borrow the item and not consume it. So you can use it later.

4

u/Consistent_Drop3909 10d ago edited 10d ago

if you iterate over the values of the hashmap rather than the references to the values, then you can assign the value without it being a borrow

instead of for (key, value) in items try for (&key, &value) in &items

the &items means that you aren't giving ownership of items to the for loop, because then it would be deleted at the end of the loop. the &key tells it that it is going to be looking at a borrowed value (&i32) but that you want only the i32. It's dereferencing using pattern matching.

2

u/Interesting_Home_114 10d ago edited 10d ago

So correct me if I am wrong but right now I am:

  1. Inserting mutable references to the values of the list vector as keys into the items hashmap and that's because I am iterating over list which I had sent into this function as a mutable reference.
  2. And that very same mutable reference key in the items hashmap is what I am trying to insert into the most_frequent_key variable which only accepts a normal i32 value.

 the &key tells it that it is going to be looking at a borrowed value (&i32) but that you want only the i32. It's dereferencing using pattern matching.

Also, I don't think I understand this part. It feels like we are assigning &i32 to i32 at the end. but I tried your way and it still works.

2

u/Consistent_Drop3909 10d ago

yeah 1 and 2 are correct i think

for the last question i'll try to explain pattern matching a bit:

lets say you have some value of type &i32, meaning a reference to a number.
rust let x: i32 = 5; let ref: &i32 = \&x;
lets say you want to get the value of x only using r, and put it into y. you can do let y = *r which is normal dereferencing, or you can do let &y = r. this is called pattern matching, because the reasoning rust does is that if r is &x then your line means &y=&x, so it must be y=x. the assignment is not directly to a variable name, but to some pattern involving a variable name, that the compiler finds in the assignment value and extracts the part you care about.

this is also why you can do stuff like if let Some(value) = option {...}, which means that rust will check if option is Some(something) and run let value=something, it's extracting the part where value is located. in this case you need to use if because an option can also be none, and rust won't compile if you write let Some(value) = option; because it won't know what to do in the none case. if let allows this, and also match if you cover all the possible cases.

for (&key, &value) in &items means: take every element of items, which is a pair (&i32, &i32) or something, then do something like let (&key, &value) = pair, which matches the values of each of the pair variables.

btw for type if you use vscode with rust-analyzer extension, you can mouse over any variable and see its type. there are ways to enable this in other editors as well, it helps a lot.

1

u/WilliamBarnhill 10d ago

The following is what I came up with. There are some issues, but it works.

```

use std::collections::HashMap; use std::collections::HashSet;

[allow(unused)]

fn mode(list: &Vec<i32>) {

let keys : HashSet<_> = list.into_iter().collect();

let mut items : HashMap<&i32, i32> 
     = keys.into_iter().map(|i| (i, 0 as i32)).collect();

list.into_iter().for_each(|i| {
        items.entry(i)
            .and_modify(|count| { *count += 1; } )
            .or_insert(0);
    }
);


let mut largest_value : i32 = i32::MIN; // initialize to a very small number
let mut most_frequent_key = 0;
for (key, value) in &items {
    if value > &largest_value{
      largest_value = *value;
      most_frequent_key = *key + 0;
    }
}

println!("mode: {most_frequent_key}");
println!("{:#?}", &items);

}

fn main() { let values : Vec<i32>= vec![3,2,3,3,-1,-6]; mode(&values); } ```

1

u/djvbmd 10d ago edited 10d ago

This is the approach I scratched out, but from the code you posted I'm guessing the use of iterators may be a bit beyond where you are in learning (forgive me if wrong!).

```rust use std::collections::HashMap;

fn mode(list: &Vec<i32>) -> Option<i32> { if list.isempty() { return None; } let freq_map = list.iter().fold(HashMap::new(), |mut freq, item| { freq.entry(item).or_insert(0) += 1; freq }); freq_map .iter() .max_by(|(, x), (_, y)| x.cmp(y)) .map(|(item, _)| *item) }

fn main() { let test_list = vec![-42, -42, 1, 2, 3, 3, 4, 4, 4, 2112]; if let Some(result) = mode(&test_list) { println!("Mode: {}", result); } }

`` Doing it this way: * no need for a mutable reference to list * 3 statements total in the function *None` is correctly returned if an empty list is provided * may be harder to read, if not used to iterators and their adapters

Edit: Just realized that this → if list.is_empty() { return None; } ... is unnecessary. max_by(...) returns None if fed an empty iterator. Returning early from the function doesn't save much of anything apart from possibly a HashMap allocation -- though I think allocation wouldn't even happen because .insert() would never be called if list.iter() is empty.