Laurence Tratt
Laurence Tratt

Reputation: 49

Why do fat pointers sometimes percolate outwards?

I thought that I had understood fat pointers in Rust, but I have a case where I can't understand why they seem to percolate outwards from an inner type. Presumably my mental model is off, but I'm struggling to come up with a satisfactory explanation for this code:

use std::cell::RefCell;
use std::fmt::Debug;
use std::mem::size_of;
use std::rc::Rc;

fn main() {
    println!("{}", size_of::<Rc<RefCell<Vec<u8>>>>());
    println!("{}", size_of::<Rc<RefCell<Debug>>>());
    println!("{}", size_of::<Box<Rc<RefCell<Debug>>>>());
}

which, on a 64-bit machine, prints 8, 16, 8. Playground link.

Since Rc makes a Box internally (using into_raw_non_null), I expected this to print 8, 8, 8. Is there a reason why, at least from size_of's perspective, the fat pointer seems to percolate outwards from Debug, even past Rc's Box? Is it because it's stored as a raw pointer perhaps?

Upvotes: 4

Views: 208

Answers (1)

Shepmaster
Shepmaster

Reputation: 431579

Ultimately, Rc<RefCell<Debug>> is the trait object and trait objects are fat pointers. The types inside and outside of it are not fat pointers.


There are no fat pointers in the Vec<u8> set, whatsoever. A Vec<T> is a (*mut T, usize, usize), a RefCell<T> is a (T, usize), and an Rc<T> is a (*mut T).

size_of              | is
---------------------+---
           Vec<u8>   | 24
   RefCell<Vec<u8>>  | 32
Rc<RefCell<Vec<u8>>> |  8

Your second and third cases do involve a fat pointer for a trait object: Rc<RefCell<dyn Debug>>. Putting a trait object behind another pointer (the Rc) creates a thin pointer to the concrete type: *mut RefCell<dyn Debug>.

size_of                     | is
----------------------------+---
    Rc<RefCell<dyn Debug>>  | 16
Box<Rc<RefCell<dyn Debug>>> |  8

Notably, it's impossible to create a RefCell<dyn Debug>:

error[E0277]: the size for values of type `dyn std::fmt::Debug` cannot be known at compilation time
 --> src/main.rs:4:20
  |
4 |     println!("{}", mem::size_of::<RefCell<dyn Debug>>());
  |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
  |
  = help: within `std::cell::RefCell<dyn std::fmt::Debug>`, the trait `std::marker::Sized` is not implemented for `dyn std::fmt::Debug`
  = note: to learn more, visit <https://doc.rust-lang.org/book/second-edition/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
  = note: required because it appears within the type `std::cell::RefCell<dyn std::fmt::Debug>`
  = note: required by `std::mem::size_of`

The trait object requires some indirection; when you add some, you've finally constructed some type of fat pointer.


You can use the unstable option -Z print-type-sizes to explore the layout of structs:

type: `std::rc::RcBox<std::cell::RefCell<dyn std::fmt::Debug>>`: 24 bytes, alignment: 8 bytes
    field `.strong`: 8 bytes
    field `.weak`: 8 bytes
    field `.value`: 8 bytes

type: `core::nonzero::NonZero<*const std::rc::RcBox<std::cell::RefCell<dyn std::fmt::Debug>>>`: 16 bytes, alignment: 8 bytes
    field `.0`: 16 bytes

type: `std::ptr::NonNull<std::rc::RcBox<std::cell::RefCell<dyn std::fmt::Debug>>>`: 16 bytes, alignment: 8 bytes
    field `.pointer`: 16 bytes

type: `std::rc::Rc<std::cell::RefCell<dyn std::fmt::Debug>>`: 16 bytes, alignment: 8 bytes
    field `.ptr`: 16 bytes
    field `.phantom`: 0 bytes, offset: 0 bytes, alignment: 1 bytes

type: `std::cell::RefCell<dyn std::fmt::Debug>`: 8 bytes, alignment: 8 bytes
    field `.borrow`: 8 bytes
    field `.value`: 0 bytes

I'm not 100% about parsing this output, as I expect RefCell<dyn Debug> to be an unsized type (as shown by the error above). I assume the meaning of "0 bytes" is overloaded.

Upvotes: 4

Related Questions