Paprika
Paprika

Reputation: 174

Why I can use dynamic dispatch with Rc but not RefCell?

I was using Rc for dynamic dispatch on an object that implements a trait. Then I needed interior mutability so I changed to RefCell. I thought that RefCell was simply an Rc for interior mutability, but it won't accept my trait object.

use std::cell::RefCell;
use std::rc::Rc;

trait Test {}

struct A;

impl Test for A {}

fn main() {
    // This works:
    let x: Rc<dyn Test> = Rc::new(A);
    
    // But this not:
    // let x: RefCell<dyn Test> = RefCell::new(A);
}

Error:

error[E0308]: mismatched types
  --> src/main.rs:18:32
   |
18 |     let x: RefCell<dyn Test> = RefCell::new(A);
   |            -----------------   ^^^^^^^^^^^^^^^ expected trait object `dyn Test`, found struct `A`
   |            |
   |            expected due to this
   |
   = note: expected struct `std::cell::RefCell<dyn Test>`
              found struct `std::cell::RefCell<A>`

error[E0277]: the size for values of type `dyn Test` cannot be known at compilation time
  --> src/main.rs:18:9
   |
18 |     let x: RefCell<dyn Test> = RefCell::new(A);
   |         ^ doesn't have a size known at compile-time
   |
   = help: within `std::cell::RefCell<dyn Test>`, the trait `std::marker::Sized` is not implemented for `dyn Test`
   = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
   = note: required because it appears within the type `std::cell::RefCell<dyn Test>`
   = note: all local variables must have a statically known size
   = help: unsized locals are gated as an unstable feature

Upvotes: 2

Views: 418

Answers (1)

Sven Marnach
Sven Marnach

Reputation: 602285

A RefCell is a wrapper struct facilitating interior mutability, while an Rc is a reference-counted smart pointer. They are quite different and serve orthogonal purposes. If you want to use a trait object with interior mutability, you can combine Rc and RefCell to Rc<RefCell<dyn Test>>.

A RefCell provides a safe mechanism to borrow the wrapped value at runtime. Rust's requirements for references – mutable borrows are exclusive – are checked when you use the borrow() or borrow_mut() methods based on a flag stored in the RefCell alongside the wrapped value.

An Rc is a reference-counted smart pointer. It allows multiple owners for a heap-allocated value. Once all references are dropped, the pointee will be deallocated. As you can see, this functionality is unrelated to checking borrows at runtime.

If you wrap a dynamically sized type like a trait object inside a RefCell, you end up with a dynamically sized RefCell. In Rust, dynamically sized types cannot be stored on the stack, so you have to put it behind some kind of reference. In this case Rc seems most appropriate, since a RefCell is most useful when combined with shared ownership.

Related question:

This section in the book provides further explanations:

Upvotes: 10

Related Questions