4

This is my code:

use std::rc::{Rc, Weak};
use std::cell::RefCell;

trait Trait {}

fn push<E: Trait>(e: E) {
    let mut v: Vec<Rc<RefCell<Box<dyn Trait>>>> = Vec::new();
    
    // let x = Rc::new(RefCell::new(Box::new(e)));
    // v.push(x); // error

    v.push(Rc::new(RefCell::new(Box::new(e)))); // works fine
}

The v.push(x) raises this error:

error[E0308]: mismatched types
  --> src/main.rs:12:12
   |
7  | fn push<E: Trait>(e: E) {
   |         - this type parameter
...
12 |     v.push(x);
   |            ^ expected trait object `dyn Trait`, found type parameter `E`
   |
   = note: expected struct `std::rc::Rc<std::cell::RefCell<std::boxed::Box<dyn Trait>>>`
              found struct `std::rc::Rc<std::cell::RefCell<std::boxed::Box<E>>>`

But if I push the value (constructed with the exact same value and types) directly into the vector it compiles without error.

So why doesn't the first version compile? And what should I change to make it so that I can use x before pushing it into the vector?

pretzelhammer
  • 13,874
  • 15
  • 47
  • 98
Nick
  • 10,309
  • 21
  • 97
  • 201
  • `Rc>>` is probably unnecessary; you can usually get away with `Rc>`. If you remove the `Box` layer, [both work](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=74a45b83650bfa8545979b3edcae3ae9) because `Rc>` will coerce to `Rc>` the same way `Box` does. – trent May 23 '20 at 14:20
  • Commenting to link to [how unsized coercions work](/q/52288980/3650362) and [another recent question whose answers you may find enlightening](/q/61799081/3650362). – trent May 23 '20 at 14:36
  • Also [How do I pass Rc>> to a function accepting Rc>>?](/q/30861295/3650362) which elaborates a little on my first comment – trent May 23 '20 at 14:41

1 Answers1

9

It's all in the type inference. When you write:

v.push(Rc::new(RefCell::new(Box::new(e))));

Rust can tell from that context that the argument to RefCell::new() must be a Box<dyn Trait>, so despite supplying a Box<E>, it coerces it to the former type. When you write this on the other hand:

let x = Rc::new(RefCell::new(Box::new(e)));
v.push(x); // compile error

Rust first infers that x of type Rc<RefCell<Box<E>>> and you can no longer push it into a vec of Rc<RefCell<Box<dyn Trait>>>. You can change this by putting an explicit type annotation in your let binding to tell Rust upfront that you really do want a Rc<RefCell<Box<dyn Trait>>>:

use std::rc::{Rc, Weak};
use std::cell::RefCell;

trait Trait {}

fn push<E: Trait>(e: E) {
    let mut v: Vec<Rc<RefCell<Box<dyn Trait>>>> = Vec::new();

    let x: Rc<RefCell<Box<dyn Trait>>> = Rc::new(RefCell::new(Box::new(e)));
    v.push(x); // compiles
}

playground

The important thing to understand here is that E is not the same as dyn Trait. E is some known concrete implementation of Trait while dyn Trait is a trait object with its underlying concrete implementation erased.

trent
  • 25,033
  • 7
  • 51
  • 90
pretzelhammer
  • 13,874
  • 15
  • 47
  • 98