简体   繁体   中英

Why does Rust not perform implicit deref coercion in match patterns?

After reading the section in the Rust book on Smart Pointers and Interior mutability , I tried, as a personal exercise, to write a function that would traverse a linked list of smart pointers and return the "last" element in the list:

#[derive(Debug, PartialEq)]
enum List {
    Cons(Rc<RefCell<i32>>, Rc<List>),
    Nil,
}

use crate::List::{Cons, Nil};

fn get_last(list: &List) -> &List {
    match list {
        Nil | Cons(_, Nil) => list,
        Cons(_, next_list) => get_last(next_list),
    }
}

This code results in the following error:

   |         Nil | Cons(_, Nil) => list,
   |                       ^^^ expected struct `std::rc::Rc`, found enum `List

I was able to get it to work by using a "match guard" and explicitly dereferncing on the Cons(_, x) pattern:

fn get_last(list: &List) -> &List {
    match list {
        Nil => list,
        Cons(_, next_list) if **next_list == Nil => list,
        Cons(_, next_list) => get_last(next_list),
    }
}

Given what I've learned about implicit dereferencing and the Deref trait implementation for Rc , I would have expected my first attempt to work. Why must I explicitly dereference in this example?

First, we need to understand what deref coercion is. If T derefs to U and x is a value of type T , then:

  • *x is *Deref::deref(&x)
  • &T can be coerced to &U
  • x.method() will check type U during method resolution.

How method resolution works is when you call a method on a type, it first checks for a method by adding nothing to the type, then adding & , then adding &mut , then derefing. So, when figuring out which method to call for x.method() , it'll first check for a method that takes T , then &T , then &mut T , then U , then &U , then &mut U ( read more here ). This does not apply to operators. Therefore, == will not coerce the differing types, which is why you must explicitly dereference.

But what if we did use a method, like .eq in the PartialEq trait? Things get interesting. The following code fails:

fn get_last(list: &List) -> &List {
    match list {
        Nil => list,
        Cons(_, next_list) if next_list.eq(Nil) => list,
        Cons(_, next_list) => get_last(next_list),
    }
}

but the following succeeds:

fn get_last(list: &List) -> &List {
    match list {
        Nil => list,
        // notice how it's Nil.eq and not next_list.eq
        Cons(_, next_list) if Nil.eq(next_list) => list,
        Cons(_, next_list) => get_last(next_list),
    }
}

Why is this? Let's look at the first example:

next_list is of type &Rc<List> , so it starts searching for a .eq method. It immediately finds one defined in the PartialEq implementation for Rc with signature fn eq(&self, other: &Rc<List>) . However, other is of type List in this case, which cannot be coerced to &Rc<List> .

Then why does the second work? Nil is of type List , so it starts searching for a .eq method. It can't find any for List , so it tries &List next, where it finds the derived PartialEq implmentation with signature fn eq(&self, other: &List) . In this case, other is of type &Rc<List> , which can be coerced to &List because of its Deref implementation. This means that everything typechecks properly and the code works.

As to why your first attempt didn't work, it appears to just not be a feature in rust, and there's a proposal to add it dating back to 2017 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM