A problem of "implicit deref coercion"?

Hello,
first of all, a clarification is needed: the title of this question might be totally incorrect/misleading; it's just my assumption.

I am a Rust beginner, and while reading "The Book," I came across the following example (Chapter 15, Listing 15-26):

use crate::List::{Cons, Nil      };
use std::cell::RefCell;
use std::rc::Rc;

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

    impl List {
      fn tail(&self) -> Option<&RefCell<Rc<List>>> {      // !!!!
        match self {
             Cons(a, item :&RefCell<Rc<List>>) => Some(item),   // !!!!
             Nil => None,
        }
      }
  }

fn main() {
      let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
      
      println!("a initial rc count = {}", Rc::strong_count(&a));
      println!("a next item = {:?}", a.tail());
      
      let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));
      
      println!("a rc count after b creation = {}", Rc::strong_count(&a));
      println!("b initial rc count = {}", Rc::strong_count(&b));
      println!("b next item = {:?}", b.tail());
      
      if let Some(link :&RefCell<Rc<List>>) = a.tail() {    // !!!!
          *link.borrow_mut() = Rc::clone(&b);
      }
      
      println!("b rc count after changing a = {}", Rc::strong_count(&b));
      println!("a rc count after changing a = {}", Rc::strong_count(&a));
      
      // Uncomment the next line to see that we have a cycle;
      // it will overflow the stack
      // println!("a next item = {:?}", a.tail());
}

I marked some lines with "// !!!! " because those are the ones that confuse me, and for the convenience of discussion, I added explicit type annotations provided by the development environment.

I believe I understand the intention of the example, but what doesn't fully make sense to me are the types I highlighted:

I would expect them to be "RefCell<Rc>" and not "&RefCell<Rc>," and of course, the issue lies in my incomplete understanding (the code works correctly, and I assume the automatically provided annotations are correct).

Can someone explain what is happening and where I am mistaken?

Thank you.

It is unclear where the misunderstanding is, so I will answer straight. It is &RefCell<Rc> and not RefCell<Rc>, because tail is declared fn tail(&self), not fn tail(self).

The problem is unrelated to implicit deref coercion.

Patterns like in a match have "binding modes". In this case, where you're matching against a reference, the a and item are bound as references to the inside of *self.

        match self {
             Cons(a, item) => Some(item),   // !!!!
             Nil => None,
        }

Binding modes can be confusing when you're trying to follow exactly what's going on. Your editor annotations are one way to help understand. Another is to use the pattern_type_mismatch Clippy lint to detect where binding modes are kicking in, and rewrite the matches to make everything explicit.

Doing so on the provided code could result in this version of the code, for example:

       fn tail(&self) -> Option<&RefCell<Rc<List>>> {
-        match self {
-             Cons(a, item) => Some(item),
+        match *self {
+             Cons(ref a, ref item) => Some(item),
              Nil => None,
         }
       }
1 Like

"Binding modes" is the Answer.

Thank you

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.