1

I am trying to learn Rust and am having some trouble understanding why we need to dereference pointers in some cases and not in others. Fishing for answers online, I found out that Rust has automatic dereferencing, but I couldn't find much on the topic. Can someone explain to me how this exactly works?

To make it more specific, here is a sample code where I encountered this problem.

fn mode(v: &mut Vec<i32>) -> Option<i32> {
    let mut map: HashMap<i32, i32> = HashMap::new();
    let mut max = (0, 0);
    if v.is_empty() {
        return None;
  
    } else {
        ();
  
    }
    for i in v {
        let count = map.entry(*i).or_insert(0);
        *count += 1;
  
    }
    for (k, v) in map.iter() {
        if v > &max.1 {
            max = (*k, *v);
           } else {
            ();
  
        }
  
    }
    Some(max.0)

Here, there is apparantely no need to dereference vector v anywhere, but we need to dereference several other variables such as i in line 75, and I do not understand why.

  • `is_empty(&self)` needs a reference on self. `v` is already a reference (`&mut Vec`), so there is no need to dereference. `i in v` calls `into_iter(self) where self is &Vec` , so there is also no need to derefence. Assigning to `(i32,i32)` needs dereferencing. – CoronA May 28 '23 at 05:11
  • 2
    @CoronA It does need to dereference, though, because the `is_empty` method comes from the slice type, not `Vec` itself. It's usable because `Vec` implements `Deref`. If the compiler didn't auto-deref, you'd have to manually deref the `Vec` to get a slice. – cdhowie May 28 '23 at 05:27
  • @cdhowie: I voted up your comment, because I did not check it. But after checking it I cannot find anything wrong with my statement: `is_empty(&self)` is implemented for `Vec` and pasting the code into my IDE also claims that this method is called. – CoronA May 28 '23 at 19:18

1 Answers1

4

The easiest way to illustrate this is within the context of the sample code x.foo(y).

x here is the receiver. Given the type of x, the compiler has to figure out what function foo is. It attempts to locate foo on x's type, and additionally by auto-dereferencing x, which is what you are asking about. This process considers the type of x as well as every type that is encountered by dereferencing x as many times as possible. (It also considers & and possibly &mut variants of these types, depending on the mutability of x or its reference type.)

If exactly one foo is found, then the call is transformed to T::foo(z, y) where T is the type the foo method was located on, and z is whatever sequence of dereferences (plus one final, optional & or &mut) results in a T, &T, or &mut T, as required by T::foo.

What about y, though? Arguments undergo coercion, which is a different thing altogether. The linked documentation lists all of the possible coercions, which I will omit for the sake of brevity, but notably absent are &T to T and &mut T to T, one of which would be required to make map.entry(i) work in your sample code. The explicit dereference is therefore required.

Note that one of the items present in the list of coercions is:

&T or &mut T to &U if T implements Deref<Target = U>.

Observe that this doesn't actually dereference a reference but rather converts the reference to another type of reference! That is, the coercion is &T to &U, not &T to U. This is what allows you to give &String to an argument that expects a &str, for example (String implements Deref<Target = str>), but this rule doesn't provide coercion from &mut i32 to i32 as would be required to make map.entry(i) work.


for i in v is a completely different scenario; this works simply because &mut Vec<T> implements IntoIterator.


So the answer as to why you don't have to dereference v is because you are using it in receiver position, where auto-dereference happens. Function arguments instead undergo coercion, which has an entirely different set of rules.

Now having said that, in your code v is already a reference to a Vec, and so v.is_empty() could be explained through coercion as well, as this method comes from slices, to which Vec automatically dereferences. However, the overall point still stands -- you cannot conflate auto-dereferencing with deref coercion. This is one instance where they would happen to do the same thing, but in many other cases they would not.

cdhowie
  • 158,093
  • 24
  • 286
  • 300