How should I understand &**self in Box

Viewed 124

Code in boxed.rs:

#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "const_box", issue = "92521")]
impl<T: ?Sized, A: Allocator> const Deref for Box<T, A> {
    type Target = T;

    fn deref(&self) -> &T {
        &**self
    }
}

In my understanding:

  • self -> &Box,
  • *self -> Box.
  • **self -> *Box, will call: *(Box.deref())? Doesn't that lead to recursion?

I create a test code:

impl<T> Deref for MyBox<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &**self
    }
}

When I run it, Rust return me: fatal runtime error: stack overflow

So how does &**self work in std's Box?

1 Answers

Box is special. It is a lang item, and the compiler knows to deref it without help. So:

  • self - &Box.
  • *self - Box, builtin deref of shared reference.
  • **self - T, builtin deref of Box.
  • &**self - &T.

Similar things happen for all builtin operations. For example, this is the Add impl for integers (inside a macro):

#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "const_ops", issue = "90080")]
impl const Add for $t {
    type Output = $t;

    #[inline]
    #[rustc_inherit_overflow_checks]
    fn add(self, other: $t) -> $t { self + other }
}

Again, the compiler knows to add integers. It is a builtin operation.

The reason we need the impl is for generics. Code like 0i32 + 0i32 translates to the builtin addition, but in a generic function:

fn add<T: Add>(a: T, b: T) -> T::Output { a + b }

If integers wouldn't implement Add, this function wouldn't accept them. The same happens for Box and Deref.

You can see that by inspecting the MIR. For example, the following function:

pub fn deref(b: &Box<i32>) -> &i32 {
    &**b
}

Generates the following MIR:

fn deref(_1: &Box<i32>) -> &i32 {
    let mut _0: &i32;
    bb0: {
        _0 = &(*(*_1));
        return;
    }
}

However, the following function:

pub fn deref<T: std::ops::Deref<Target = i32>>(b: &T) -> &i32 {
    &**b
}

Generates the following MIR, that calls deref():

fn deref(_1: &T) -> &i32 {
    let mut _0: &i32;
    let _2: &i32;
    let mut _3: &T;
    bb0: {
        _3 = _1;
        _2 = <T as Deref>::deref(move _3) -> bb1;
    }
    bb1: {
        _0 = _2;
        return;
    }
}

The same happens for other Deref implementors, e.g. Rc:

pub fn deref(b: &std::rc::Rc<i32>) -> &i32 {
    &**b
}
fn deref(_1: &Rc<i32>) -> &i32 {
    let mut _0: &i32;
    let _2: &i32;
    let mut _3: &std::rc::Rc<i32>;
    bb0: {
        _3 = _1;
        _2 = <Rc<i32> as Deref>::deref(move _3) -> bb1;
    }
    bb1: {
        _0 = _2;
        return;
    }
}
Related